mirror of
https://github.com/instructkr/claude-code.git
synced 2026-06-05 12:06:43 +00:00
fix: expose complete version provenance
Generated with https://github.com/Yeachan-Heo/gajae-code Co-authored-by: Gajae Code <dev@gajae-code.com>
This commit is contained in:
@@ -1,10 +1,9 @@
|
||||
use std::env;
|
||||
use std::process::Command;
|
||||
|
||||
fn main() {
|
||||
// Get git SHA (short hash)
|
||||
let git_sha = Command::new("git")
|
||||
.args(["rev-parse", "--short", "HEAD"])
|
||||
fn command_output(program: &str, args: &[&str]) -> Option<String> {
|
||||
Command::new(program)
|
||||
.args(args)
|
||||
.output()
|
||||
.ok()
|
||||
.and_then(|output| {
|
||||
@@ -14,11 +13,37 @@ fn main() {
|
||||
None
|
||||
}
|
||||
})
|
||||
.map_or_else(|| "unknown".to_string(), |s| s.trim().to_string());
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let git_sha =
|
||||
command_output("git", &["rev-parse", "HEAD"]).unwrap_or_else(|| "unknown".to_string());
|
||||
let git_sha_short = command_output("git", &["rev-parse", "--short=12", "HEAD"])
|
||||
.or_else(|| git_sha.get(..git_sha.len().min(12)).map(str::to_string))
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
let git_dirty = command_output("git", &["status", "--porcelain"])
|
||||
.map(|status| (!status.trim().is_empty()).to_string())
|
||||
.unwrap_or_else(|| "false".to_string());
|
||||
let git_branch = command_output("git", &["branch", "--show-current"])
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
let git_commit_date = command_output("git", &["show", "-s", "--format=%cI", "HEAD"])
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
let git_commit_timestamp = command_output("git", &["show", "-s", "--format=%ct", "HEAD"])
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
let rustc_version =
|
||||
command_output("rustc", &["--version"]).unwrap_or_else(|| "unknown".to_string());
|
||||
|
||||
println!("cargo:rustc-env=GIT_SHA={git_sha}");
|
||||
println!("cargo:rustc-env=GIT_SHA_SHORT={git_sha_short}");
|
||||
println!("cargo:rustc-env=GIT_DIRTY={git_dirty}");
|
||||
println!("cargo:rustc-env=GIT_BRANCH={git_branch}");
|
||||
println!("cargo:rustc-env=GIT_COMMIT_DATE={git_commit_date}");
|
||||
println!("cargo:rustc-env=GIT_COMMIT_TIMESTAMP={git_commit_timestamp}");
|
||||
println!("cargo:rustc-env=RUSTC_VERSION={rustc_version}");
|
||||
|
||||
// TARGET is always set by Cargo during build
|
||||
// TARGET is always set by Cargo during build.
|
||||
let target = env::var("TARGET").unwrap_or_else(|_| "unknown".to_string());
|
||||
println!("cargo:rustc-env=TARGET={target}");
|
||||
|
||||
@@ -35,23 +60,12 @@ fn main() {
|
||||
})
|
||||
.or_else(|| std::env::var("BUILD_DATE").ok())
|
||||
.unwrap_or_else(|| {
|
||||
// Fall back to current date via `date` command
|
||||
Command::new("date")
|
||||
.args(["+%Y-%m-%d"])
|
||||
.output()
|
||||
.ok()
|
||||
.and_then(|o| {
|
||||
if o.status.success() {
|
||||
String::from_utf8(o.stdout).ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.map_or_else(|| "unknown".to_string(), |s| s.trim().to_string())
|
||||
command_output("date", &["+%Y-%m-%d"]).unwrap_or_else(|| "unknown".to_string())
|
||||
});
|
||||
println!("cargo:rustc-env=BUILD_DATE={build_date}");
|
||||
|
||||
// Rerun if git state changes
|
||||
println!("cargo:rerun-if-changed=.git/HEAD");
|
||||
println!("cargo:rerun-if-changed=.git/refs");
|
||||
// Rerun if git state changes. Paths are relative to this package root.
|
||||
println!("cargo:rerun-if-changed=../../../.git/HEAD");
|
||||
println!("cargo:rerun-if-changed=../../../.git/refs");
|
||||
println!("cargo:rerun-if-changed=../../../.git/index");
|
||||
}
|
||||
|
||||
@@ -268,6 +268,12 @@ const DEFAULT_OAUTH_CALLBACK_PORT: u16 = 4545;
|
||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
const BUILD_TARGET: Option<&str> = option_env!("TARGET");
|
||||
const GIT_SHA: Option<&str> = option_env!("GIT_SHA");
|
||||
const GIT_SHA_SHORT: Option<&str> = option_env!("GIT_SHA_SHORT");
|
||||
const GIT_DIRTY: Option<&str> = option_env!("GIT_DIRTY");
|
||||
const GIT_BRANCH: Option<&str> = option_env!("GIT_BRANCH");
|
||||
const GIT_COMMIT_DATE: Option<&str> = option_env!("GIT_COMMIT_DATE");
|
||||
const GIT_COMMIT_TIMESTAMP: Option<&str> = option_env!("GIT_COMMIT_TIMESTAMP");
|
||||
const RUSTC_VERSION: Option<&str> = option_env!("RUSTC_VERSION");
|
||||
const INTERNAL_PROGRESS_HEARTBEAT_INTERVAL: Duration = Duration::from_secs(3);
|
||||
const POST_TOOL_STALL_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
const PRIMARY_SESSION_EXTENSION: &str = "jsonl";
|
||||
@@ -4452,9 +4458,15 @@ fn version_json_value() -> serde_json::Value {
|
||||
"kind": "version",
|
||||
"action": "show",
|
||||
"status": "ok",
|
||||
"message": render_version_report(),
|
||||
"human_readable": render_version_report(),
|
||||
"version": VERSION,
|
||||
"git_sha": binary_provenance.git_sha,
|
||||
"git_sha_short": binary_provenance.git_sha_short,
|
||||
"is_dirty": binary_provenance.is_dirty,
|
||||
"branch": binary_provenance.branch,
|
||||
"commit_date": binary_provenance.commit_date,
|
||||
"commit_timestamp": binary_provenance.commit_timestamp,
|
||||
"rustc_version": binary_provenance.rustc_version,
|
||||
"target": binary_provenance.target,
|
||||
"build_date": binary_provenance.build_date,
|
||||
"executable_path": binary_provenance.executable_path,
|
||||
@@ -4693,6 +4705,12 @@ struct StatusContext {
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct BinaryProvenance {
|
||||
git_sha: Option<String>,
|
||||
git_sha_short: Option<String>,
|
||||
is_dirty: bool,
|
||||
branch: Option<String>,
|
||||
commit_date: String,
|
||||
commit_timestamp: i64,
|
||||
rustc_version: String,
|
||||
target: Option<String>,
|
||||
build_date: String,
|
||||
executable_path: Option<String>,
|
||||
@@ -4714,6 +4732,12 @@ impl BinaryProvenance {
|
||||
json!({
|
||||
"status": self.status(),
|
||||
"git_sha": self.git_sha,
|
||||
"git_sha_short": self.git_sha_short,
|
||||
"is_dirty": self.is_dirty,
|
||||
"branch": self.branch,
|
||||
"commit_date": self.commit_date,
|
||||
"commit_timestamp": self.commit_timestamp,
|
||||
"rustc_version": self.rustc_version,
|
||||
"target": self.target,
|
||||
"build_date": self.build_date,
|
||||
"executable_path": self.executable_path,
|
||||
@@ -4733,18 +4757,35 @@ fn known_build_metadata(value: Option<&str>) -> Option<String> {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_build_bool(value: Option<&str>) -> bool {
|
||||
value
|
||||
.map(str::trim)
|
||||
.is_some_and(|value| value.eq_ignore_ascii_case("true") || value == "1")
|
||||
}
|
||||
|
||||
fn parse_build_timestamp(value: Option<&str>) -> i64 {
|
||||
value
|
||||
.and_then(|value| value.trim().parse::<i64>().ok())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
fn binary_provenance_for(cwd: Option<&Path>) -> BinaryProvenance {
|
||||
let git_sha = known_build_metadata(GIT_SHA);
|
||||
let git_sha_short = known_build_metadata(GIT_SHA_SHORT).or_else(|| {
|
||||
git_sha
|
||||
.as_ref()
|
||||
.map(|sha| sha.chars().take(12).collect::<String>())
|
||||
});
|
||||
let target = known_build_metadata(BUILD_TARGET);
|
||||
let workspace_git_sha = cwd.and_then(|cwd| {
|
||||
run_git_capture_in(cwd, &["rev-parse", "--short", "HEAD"])
|
||||
run_git_capture_in(cwd, &["rev-parse", "HEAD"])
|
||||
.map(|sha| sha.trim().to_string())
|
||||
.filter(|sha| !sha.is_empty())
|
||||
});
|
||||
let workspace_match = git_sha
|
||||
.as_deref()
|
||||
.zip(workspace_git_sha.as_deref())
|
||||
.map(|(binary, workspace)| binary.starts_with(workspace) || workspace.starts_with(binary));
|
||||
.map(|(binary, workspace)| binary == workspace);
|
||||
let hint = if git_sha.is_none() {
|
||||
Some(
|
||||
"Build metadata did not include a git SHA; rebuild from a git checkout before filing provenance-sensitive dogfood reports."
|
||||
@@ -4760,6 +4801,12 @@ fn binary_provenance_for(cwd: Option<&Path>) -> BinaryProvenance {
|
||||
};
|
||||
BinaryProvenance {
|
||||
git_sha,
|
||||
git_sha_short,
|
||||
is_dirty: parse_build_bool(GIT_DIRTY),
|
||||
branch: known_build_metadata(GIT_BRANCH),
|
||||
commit_date: known_build_metadata(GIT_COMMIT_DATE).unwrap_or_else(|| "unknown".to_string()),
|
||||
commit_timestamp: parse_build_timestamp(GIT_COMMIT_TIMESTAMP),
|
||||
rustc_version: known_build_metadata(RUSTC_VERSION).unwrap_or_else(|| "unknown".to_string()),
|
||||
target,
|
||||
build_date: DEFAULT_DATE.to_string(),
|
||||
executable_path: env::current_exe()
|
||||
@@ -10285,10 +10332,12 @@ fn parse_titled_body(value: &str) -> Option<(String, String)> {
|
||||
}
|
||||
|
||||
fn render_version_report() -> String {
|
||||
let git_sha = GIT_SHA.unwrap_or("unknown");
|
||||
let git_sha = GIT_SHA_SHORT.or(GIT_SHA).unwrap_or("unknown");
|
||||
let target = BUILD_TARGET.unwrap_or("unknown");
|
||||
let branch = GIT_BRANCH.unwrap_or("unknown");
|
||||
let dirty = GIT_DIRTY.unwrap_or("unknown");
|
||||
format!(
|
||||
"Claw Code\n Version {VERSION}\n Git SHA {git_sha}\n Target {target}\n Build date {DEFAULT_DATE}"
|
||||
"Claw Code\n Version {VERSION}\n Git SHA {git_sha}\n Branch {branch}\n Dirty {dirty}\n Target {target}\n Build date {DEFAULT_DATE}"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -270,29 +270,88 @@ fn version_emits_json_when_requested() {
|
||||
"version JSON must have action:show (#711)"
|
||||
);
|
||||
assert_eq!(parsed["version"], env!("CARGO_PKG_VERSION"));
|
||||
// Provenance fields must be present for binary identification (#507).
|
||||
// Provenance fields must be present for binary identification (#507/#437).
|
||||
assert!(
|
||||
parsed.get("message").is_none(),
|
||||
"version JSON should not duplicate the text report in legacy message; use human_readable instead: {parsed}"
|
||||
);
|
||||
assert!(
|
||||
parsed["human_readable"]
|
||||
.as_str()
|
||||
.is_some_and(|text| text.contains("Claw Code")),
|
||||
"version JSON should keep text output only in human_readable: {parsed}"
|
||||
);
|
||||
let git_sha = parsed["git_sha"]
|
||||
.as_str()
|
||||
.expect("git_sha must be the full build commit SHA in version JSON");
|
||||
assert_eq!(git_sha.len(), 40, "git_sha must not be truncated: {parsed}");
|
||||
assert!(
|
||||
git_sha.chars().all(|ch| ch.is_ascii_hexdigit()),
|
||||
"git_sha must be a hex commit id: {parsed}"
|
||||
);
|
||||
let git_sha_short = parsed["git_sha_short"]
|
||||
.as_str()
|
||||
.expect("version JSON should expose the short SHA as a separate derived field");
|
||||
assert!(
|
||||
git_sha.starts_with(git_sha_short),
|
||||
"git_sha_short should derive from git_sha: {parsed}"
|
||||
);
|
||||
assert!(
|
||||
parsed["is_dirty"].is_boolean(),
|
||||
"is_dirty should be boolean: {parsed}"
|
||||
);
|
||||
assert!(
|
||||
parsed["branch"].is_string() || parsed["branch"].is_null(),
|
||||
"branch should be string|null: {parsed}"
|
||||
);
|
||||
assert!(
|
||||
parsed["commit_date"]
|
||||
.as_str()
|
||||
.is_some_and(|date| date != "unknown" && date.contains('T')),
|
||||
"commit_date should be an ISO-8601 commit timestamp string: {parsed}"
|
||||
);
|
||||
assert!(
|
||||
parsed["commit_timestamp"].as_i64().is_some_and(|ts| ts > 0),
|
||||
"commit_timestamp should be a positive Unix timestamp: {parsed}"
|
||||
);
|
||||
assert!(
|
||||
parsed["rustc_version"]
|
||||
.as_str()
|
||||
.is_some_and(|version| version.starts_with("rustc ")),
|
||||
"rustc_version should identify the compiler: {parsed}"
|
||||
);
|
||||
assert!(
|
||||
parsed["build_date"].is_string(),
|
||||
"build_date must be a string in version JSON"
|
||||
);
|
||||
assert!(
|
||||
parsed["executable_path"].is_string(),
|
||||
"executable_path must be a string in version JSON so callers can identify which binary is running"
|
||||
parsed["executable_path"].as_str().is_some_and(|path| !path.is_empty()),
|
||||
"executable_path must be a runtime path string so callers can identify which binary is running"
|
||||
);
|
||||
let binary_provenance = parsed["binary_provenance"]
|
||||
.as_object()
|
||||
.expect("version JSON must include binary_provenance object (#797)");
|
||||
.expect("version JSON must include binary_provenance object (#797/#437)");
|
||||
assert!(matches!(
|
||||
binary_provenance["status"].as_str(),
|
||||
Some("known" | "unknown")
|
||||
));
|
||||
assert_eq!(binary_provenance["git_sha"], parsed["git_sha"]);
|
||||
assert_eq!(binary_provenance["target"], parsed["target"]);
|
||||
assert_eq!(binary_provenance["build_date"], parsed["build_date"]);
|
||||
assert_eq!(
|
||||
binary_provenance["executable_path"],
|
||||
parsed["executable_path"]
|
||||
);
|
||||
for key in [
|
||||
"git_sha",
|
||||
"git_sha_short",
|
||||
"is_dirty",
|
||||
"branch",
|
||||
"commit_date",
|
||||
"commit_timestamp",
|
||||
"rustc_version",
|
||||
"target",
|
||||
"build_date",
|
||||
"executable_path",
|
||||
] {
|
||||
assert_eq!(
|
||||
binary_provenance[key], parsed[key],
|
||||
"binary_provenance.{key} should mirror top-level version field"
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
binary_provenance["hint"].is_string() || binary_provenance["hint"].is_null(),
|
||||
"binary provenance must classify missing/stale lineage with a structured hint field"
|
||||
@@ -334,6 +393,14 @@ fn version_status_doctor_include_binary_provenance_797() {
|
||||
version["binary_provenance"]["workspace_match"].is_boolean()
|
||||
|| version["binary_provenance"]["workspace_match"].is_null()
|
||||
);
|
||||
let workspace_git_sha = version["binary_provenance"]["workspace_git_sha"]
|
||||
.as_str()
|
||||
.expect("workspace git sha should be a string");
|
||||
assert_eq!(
|
||||
workspace_git_sha.len(),
|
||||
40,
|
||||
"workspace_git_sha should be a full SHA, not a truncated prefix: {version}"
|
||||
);
|
||||
|
||||
let status = assert_json_command(&root, &["--output-format", "json", "status"]);
|
||||
assert_eq!(status["kind"], "status");
|
||||
@@ -1518,6 +1585,11 @@ fn resumed_version_and_init_emit_structured_json_when_requested() {
|
||||
);
|
||||
assert_eq!(version["kind"], "version");
|
||||
assert_eq!(version["version"], env!("CARGO_PKG_VERSION"));
|
||||
assert!(
|
||||
version.get("message").is_none(),
|
||||
"resumed /version JSON should not include legacy prose message: {version}"
|
||||
);
|
||||
assert!(version["human_readable"].as_str().is_some());
|
||||
|
||||
let init = assert_json_command(
|
||||
&root,
|
||||
|
||||
@@ -463,6 +463,9 @@ fn resumed_version_command_emits_structured_json() {
|
||||
assert!(parsed["version"].as_str().is_some());
|
||||
assert!(parsed["git_sha"].as_str().is_some());
|
||||
assert!(parsed["target"].as_str().is_some());
|
||||
assert!(parsed["git_sha_short"].as_str().is_some());
|
||||
assert!(parsed.get("message").is_none());
|
||||
assert!(parsed["human_readable"].as_str().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user