Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Trajectory: Avoid rebuilding native core when Git index changes

> **Status:** ✅ Completed
> **Confidence:** 95%
> **Started:** November 4, 2026 at 08:29 AM
> **Completed:** November 4, 2026 at 08:32 AM

---

## Summary

Removed unused Git index watch; HEAD/ref metadata preserved. Actual build-script regression passes for staging, branch commits, detached commits and explicit releases. Controlled index-only release rebuild fell from 78.693s to 0.317s (272 artifacts fresh). Local benchmark only; clean CI recompilation and version metadata remain separate work.

**Approach:** Standard approach

---

## Key Decisions

### Watch only Git inputs that affect embedded revision
- **Chose:** Watch only Git inputs that affect embedded revision
- **Reasoning:** Cargo fingerprint logs show touching only the builder Git index recompiles both public and private core (65.813 seconds versus 0.441 seconds unchanged). HEAD and its active ref remain watched because revision metadata depends on commits; the index never affects build output.

---

## Chapters

### 1. Initial work
*Agent: codex-relay-desktop-speed-up*

- Watch only Git inputs that affect embedded revision: Watch only Git inputs that affect embedded revision
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"id": "traj_hynv0vdcl7gp",
"version": 1,
"task": {
"title": "Avoid rebuilding native core when Git index changes"
},
"status": "completed",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This completed trajectory is missing its generated .trace.json companion. Run the completion/finalization flow that emits the raw trace rather than committing only the summary and trajectory snapshot.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At .agentworkforce/trajectories/codex-build-watch/completed/2026-11/traj_hynv0vdcl7gp/trajectory.json, line 7:

<comment>This completed trajectory is missing its generated `.trace.json` companion. Run the completion/finalization flow that emits the raw trace rather than committing only the summary and trajectory snapshot.</comment>

<file context>
@@ -0,0 +1,53 @@
+  "task": {
+    "title": "Avoid rebuilding native core when Git index changes"
+  },
+  "status": "completed",
+  "startedAt": "2026-11-04T16:29:37.671Z",
+  "completedAt": "2026-11-04T16:32:57.025Z",
</file context>

"startedAt": "2026-11-04T16:29:37.671Z",
"completedAt": "2026-11-04T16:32:57.025Z",
"agents": [
{
"name": "codex-relay-desktop-speed-up",
"role": "lead",
"joinedAt": "2026-11-04T16:29:37.707Z"
}
],
"chapters": [
{
"id": "chap_44p6lwqnawlw",
"title": "Initial work",
"agentName": "codex-relay-desktop-speed-up",
"startedAt": "2026-11-04T16:29:37.707Z",
"endedAt": "2026-11-04T16:32:57.025Z",
"events": [
{
"ts": 1793809796345,
"type": "decision",
"content": "Watch only Git inputs that affect embedded revision: Watch only Git inputs that affect embedded revision",
"raw": {
"question": "Watch only Git inputs that affect embedded revision",
"chosen": "Watch only Git inputs that affect embedded revision",
"alternatives": [],
"reasoning": "Cargo fingerprint logs show touching only the builder Git index recompiles both public and private core (65.813 seconds versus 0.441 seconds unchanged). HEAD and its active ref remain watched because revision metadata depends on commits; the index never affects build output."
},
"significance": "high"
}
]
}
],
"retrospective": {
"summary": "Removed unused Git index watch; HEAD/ref metadata preserved. Actual build-script regression passes for staging, branch commits, detached commits and explicit releases. Controlled index-only release rebuild fell from 78.693s to 0.317s (272 artifacts fresh). Local benchmark only; clean CI recompilation and version metadata remain separate work.",
"approach": "Standard approach",
"confidence": 0.95
},
"commits": [],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This completed trajectory omits the implementation commit and every changed path, so its audit record cannot identify the work it completed. Regenerate the completed trajectory metadata after recording the implementation commit instead of leaving commits and filesChanged empty.

(Based on your team's feedback about complete trajectory trace metadata.) [70713d7e-b5b6-4c02-88cf-a284a308fd60]

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At .agentworkforce/trajectories/codex-build-watch/completed/2026-11/traj_hynv0vdcl7gp/trajectory.json, line 45:

<comment>This completed trajectory omits the implementation commit and every changed path, so its audit record cannot identify the work it completed. Regenerate the completed trajectory metadata after recording the implementation commit instead of leaving `commits` and `filesChanged` empty.

(Based on your team's feedback about complete trajectory trace metadata.) [70713d7e-b5b6-4c02-88cf-a284a308fd60]</comment>

<file context>
@@ -0,0 +1,53 @@
+    "approach": "Standard approach",
+    "confidence": 0.95
+  },
+  "commits": [],
+  "filesChanged": [],
+  "projectId": "AgentWorkforce/relay",
</file context>

"filesChanged": [],
"projectId": "AgentWorkforce/relay",
"tags": [],
"_trace": {
"startRef": "ad983d7278bab88b0a7d541494521146b869f78a",
"endRef": "ad983d7278bab88b0a7d541494521146b869f78a"
}
}
7 changes: 3 additions & 4 deletions crates/relay-node/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,10 @@ fn main() {
println!("cargo:rerun-if-changed=build_site.rs");
println!("cargo:rerun-if-changed=Cargo.lock");
println!("cargo:rerun-if-changed=build_version.rs");
// The revision depends on HEAD and its ref, not the staging index.
// Resolve through git so linked worktrees also track their own HEAD.
for name in ["HEAD", "index"] {
if let Some(path) = git_path(name) {
println!("cargo:rerun-if-changed={}", path.display());
}
if let Some(path) = git_path("HEAD") {
println!("cargo:rerun-if-changed={}", path.display());
}
if let Some(reference) = git(&["symbolic-ref", "-q", "HEAD"]) {
if let Some(path) = git_path(&reference) {
Expand Down
241 changes: 241 additions & 0 deletions crates/relay-node/tests/build_watch_inputs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
//! Exercise the actual build script without compiling the relay dependency graph.

use std::{
fs,
path::{Path, PathBuf},
process::{Command, Output},
time::{SystemTime, UNIX_EPOCH},
};

struct Fixture(PathBuf);

impl Fixture {
fn new() -> Self {
let nonce = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
let path =
std::env::temp_dir().join(format!("relay-build-watch-{}-{nonce}", std::process::id()));
fs::create_dir(&path).unwrap();
Self(path.canonicalize().unwrap())
}
}

impl Drop for Fixture {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.0);
}
}

fn successful(command: &mut Command) -> String {
let output: Output = command.output().expect("run build fixture command");
assert!(
output.status.success(),
"{command:?} failed:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
String::from_utf8(output.stdout).unwrap()
}

fn git(repo: &Path, args: &[&str]) -> String {
successful(
Command::new("git")
.arg("-C")
.arg(repo)
.args([
"-c",
"user.name=Build test",
"-c",
"user.email=build-test@example.invalid",
])
.args(["-c", "commit.gpgsign=false"])
.arg("-c")
.arg(format!(
"core.hooksPath={}",
repo.join(".git/no-hooks").display()
))
.env_remove("GIT_DIR")
.env_remove("GIT_WORK_TREE")
.env_remove("GIT_INDEX_FILE")
.args(args),
)
.trim()
.to_owned()
}

fn build_script(executable: &Path, runner: &Path, repo: &Path, release: Option<&str>) -> String {
let mut command = Command::new(executable);
command
.current_dir(runner)
.env_remove("AGENT_RELAY_PROBE_VERSION")
.env_remove("AGENT_RELAY_PROBE_BASE_VERSION")
.env_remove("AGENT_RELAY_SITE_URL")
.env_remove("AGENT_RELAY_URL_SCHEME")
.env_remove("AGENT_RELAY_WINDOWS_ICON")
.env_remove("GIT_DIR")
.env_remove("GIT_WORK_TREE")
.env_remove("GIT_INDEX_FILE")
.env("CARGO_PKG_VERSION", "0.1.0")
.env("CARGO_CFG_TARGET_OS", std::env::consts::OS)
.env("AGENT_RELAY_PROBE_GIT_DIR", repo)
.env("AGENT_RELAY_PROBE_CHANGELOG", repo.join("CHANGELOG.md"));
if let Some(version) = release {
// A release override must not read or watch the development changelog.
command.env("AGENT_RELAY_PROBE_VERSION", version).env(
"AGENT_RELAY_PROBE_CHANGELOG",
repo.join("missing-changelog.md"),
);
}
successful(&mut command)
}

fn stamped<'a>(output: &'a str, name: &str) -> &'a str {
let prefix = format!("cargo:rustc-env={name}=");
output
.lines()
.find_map(|line| line.strip_prefix(&prefix))
.unwrap()
}

fn assert_git_watches(output: &str, repo: &Path, runner: &Path, attached: bool) {
let paths: Vec<_> = output
.lines()
.filter_map(|line| line.strip_prefix("cargo:rerun-if-changed="))
.map(PathBuf::from)
.map(|path| {
if path.is_absolute() {
path
} else {
runner.join(path)
}
})
.filter_map(|path| path.canonicalize().ok())
.collect();
let git_path = |name: &str| {
let path = PathBuf::from(git(repo, &["rev-parse", "--git-path", name]));
let path = if path.is_absolute() {
path
} else {
repo.join(path)
};
path.canonicalize().expect("Git fixture metadata exists")
};
assert!(
paths.contains(&git_path("HEAD")),
"HEAD must still invalidate build identity"
);
if attached {
let reference = git(repo, &["symbolic-ref", "HEAD"]);
assert!(
paths.contains(&git_path(&reference)),
"commits must still invalidate build identity"
);
} else {
assert!(
!paths.contains(&git_path("refs/heads/main")),
"a detached build must not watch the previous branch"
);
}
assert!(
!paths.contains(&git_path("index")),
"staging does not change embedded identity"
);
}

#[test]
fn index_changes_preserve_identity_and_commits_update_it() {
let fixture = Fixture::new();
let repo = fixture.0.join("builder");
let runner = fixture.0.join("runner");
fs::create_dir(&repo).unwrap();
fs::create_dir(&runner).unwrap();
git(&repo, &["init", "--quiet"]);
git(&repo, &["symbolic-ref", "HEAD", "refs/heads/main"]);
fs::write(repo.join("CHANGELOG.md"), "## v2026.11.4\n\n- A release.\n").unwrap();
git(&repo, &["add", "CHANGELOG.md"]);
git(&repo, &["commit", "--quiet", "-m", "initial"]);
let original_revision = git(&repo, &["rev-parse", "--short=12", "HEAD"]);

let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let executable = fixture
.0
.join(format!("build-script{}", std::env::consts::EXE_SUFFIX));
successful(
Command::new(std::env::var_os("RUSTC").unwrap_or_else(|| "rustc".into()))
.arg("--edition=2021")
.arg(manifest.join("build.rs"))
.arg("-o")
.arg(&executable),
);
let development = build_script(&executable, &runner, &repo, None);
assert_git_watches(&development, &repo, &runner, true);
assert_eq!(
stamped(&development, "RELAY_DESKTOP_VERSION"),
format!("2026.11.4-dev+{original_revision}")
);
assert_eq!(
stamped(&development, "RELAY_DESKTOP_REVISION"),
original_revision
);

let release_version = "2026.11.5-beta.2";
let release = build_script(&executable, &runner, &repo, Some(release_version));
assert_git_watches(&release, &repo, &runner, true);
assert_eq!(stamped(&release, "RELAY_DESKTOP_VERSION"), release_version);
assert_eq!(
stamped(&release, "RELAY_DESKTOP_REVISION"),
original_revision
);
assert!(!release
.lines()
.any(|line| line.starts_with("cargo:rerun-if-changed=") && line.contains("changelog")));

fs::write(repo.join("staged.txt"), "only the index changes\n").unwrap();
git(&repo, &["add", "staged.txt"]);
assert_eq!(build_script(&executable, &runner, &repo, None), development);
assert_eq!(
build_script(&executable, &runner, &repo, Some(release_version)),
release
);

git(&repo, &["commit", "--quiet", "-m", "next source revision"]);
let revision = git(&repo, &["rev-parse", "--short=12", "HEAD"]);
assert_ne!(revision, original_revision);
let development = build_script(&executable, &runner, &repo, None);
assert_eq!(
stamped(&development, "RELAY_DESKTOP_VERSION"),
format!("2026.11.4-dev+{revision}")
);
let release = build_script(&executable, &runner, &repo, Some(release_version));
assert_eq!(stamped(&release, "RELAY_DESKTOP_VERSION"), release_version);
assert_eq!(stamped(&release, "RELAY_DESKTOP_REVISION"), revision);
assert_git_watches(&release, &repo, &runner, true);

git(&repo, &["checkout", "--quiet", "--detach", "HEAD"]);
let detached = build_script(&executable, &runner, &repo, Some(release_version));
assert_git_watches(&detached, &repo, &runner, false);
assert_eq!(stamped(&detached, "RELAY_DESKTOP_VERSION"), release_version);
assert_eq!(stamped(&detached, "RELAY_DESKTOP_REVISION"), revision);

fs::write(repo.join("detached.txt"), "commit without a branch ref\n").unwrap();
git(&repo, &["add", "detached.txt"]);
git(
&repo,
&["commit", "--quiet", "-m", "detached source revision"],
);
let detached_revision = git(&repo, &["rev-parse", "--short=12", "HEAD"]);
assert_ne!(detached_revision, revision);
let detached = build_script(&executable, &runner, &repo, Some(release_version));
assert_git_watches(&detached, &repo, &runner, false);
assert_eq!(
stamped(&detached, "RELAY_DESKTOP_REVISION"),
detached_revision
);
let detached_development = build_script(&executable, &runner, &repo, None);
assert_eq!(
stamped(&detached_development, "RELAY_DESKTOP_VERSION"),
format!("2026.11.4-dev+{detached_revision}")
);
}
Loading