fix(missions): commit under the pipeline's own git identity
ci / gates (push) Failing after 6s
ci / rust (push) Skipped
ci / frontend (push) Skipped
ci / e2e (push) Skipped
ci / publish (push) Skipped

Mission 019fc450 lost its first phase to:

  git commit → exit 128: Author identity unknown

The server container has no git identity — `git config --global
user.email` exits 1 — so any commit fails unless one is supplied.

This is the third consecutive failure whose trigger was agent behaviour
rather than our code. Earlier missions committed only because an agent
had happened to run `git config user.email` in the checkout, leaving a
local identity the server inherited. Alongside the object-permission
split and the reset, the pattern is the same: delivery depended on
incidental side effects of what an agent chose to do, so identical
missions succeeded or failed for reasons invisible in our code.

Supplied via GIT_AUTHOR_*/GIT_COMMITTER_* env on every git call, which
overrides config without a leaked string per invocation and names the
committer as the pipeline. Agents' own commits keep the identity they set.

The test asserts the identity *overrides* an existing local config rather
than trying to unset the developer's global — an override necessarily
also applies when config is absent, and it does not race parallel tests.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
Omar Sobh
2026-08-02 14:19:27 -07:00
co-authored by Claude Opus 5
parent 08b2adae23
commit 25d9805806
2 changed files with 83 additions and 0 deletions
+57
View File
@@ -736,3 +736,60 @@ async fn a_checkout_is_writable_by_both_uids_that_share_it() {
}
assert!(checked > 0, "no object directories were created to check");
}
/// Delivery must commit without depending on the checkout's git identity.
///
/// The server container has no identity of its own (`git config --global
/// user.email` exits 1), so `git commit` fails with "Author identity unknown"
/// unless one is supplied. Mission `019fc450` lost its first phase that way,
/// while earlier missions committed fine — because their agents had happened
/// to run `git config user.email` in the checkout first.
///
/// A test process cannot unset the developer's global git config without
/// racing every other test, so this asserts the stronger, deterministic
/// property: the pipeline's identity is used even when the checkout already
/// has a different one. An identity that overrides existing config is
/// necessarily also present when config is absent.
#[tokio::test]
async fn delivery_commits_under_its_own_identity() {
let pool = cm_testkit::test_pool().await;
let tmp = tempfile::tempdir().unwrap();
let mission = Uuid::now_v7();
let repo = seed_repo(tmp.path(), mission);
let (_, phase) = seed_mission_phase(&pool, mission).await;
// seed_repo configures "Test <[email protected]>" locally; the base
// commit therefore carries it, and the delivery commit must not.
std::fs::write(repo.join("IDENTITY_PROBE.md"), "PROBE\n").unwrap();
let cap = capture(&pool, tmp.path(), mission, phase)
.await
.unwrap()
.unwrap();
let commit = cap.committed.expect("delivery committed");
let author = Command::new("git")
.arg("-C")
.arg(&repo)
.args(["log", "-1", "--format=%an <%ae>", &commit.sha])
.output()
.unwrap();
let author = String::from_utf8_lossy(&author.stdout).trim().to_string();
assert_eq!(
author, "ClawMates delivery <[email protected]>",
"delivery must supply its own identity, not inherit whatever the \
checkout happens to have configured"
);
let base_author = Command::new("git")
.arg("-C")
.arg(&repo)
.args(["log", "-1", "--format=%an", "HEAD~1"])
.output()
.unwrap();
assert_eq!(
String::from_utf8_lossy(&base_author.stdout).trim(),
"Test",
"the pre-existing local identity is still configured, so the \
assertion above proves an override rather than an absence"
);
}