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
+26
View File
@@ -67,6 +67,12 @@ const EXCLUDED_PATHS: &[&str] = &[
/// operator needs to see to work out what happened.
const MAX_PATCH_BYTES: usize = 4 * 1024 * 1024;
/// Who delivery commits as. Not a real mailbox — it identifies the pipeline in
/// `git log` and keeps commits attributable to the platform rather than to
/// whichever agent last touched the checkout's git config.
const COMMIT_IDENTITY_NAME: &str = "ClawMates delivery";
const COMMIT_IDENTITY_EMAIL: &str = "[email protected]";
/// Ceiling on the gate's test run. Long enough for a real suite, short enough
/// that a hung test does not hold a phase open indefinitely.
const TEST_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(900);
@@ -382,6 +388,26 @@ async fn git(repo: &Path, args: &[&str]) -> Result<String, String> {
full.extend_from_slice(args);
let out = tokio::process::Command::new("git")
.args(&full)
// The server container has no git identity — `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 to exactly that.
//
// This was the third failure in a row whose trigger was *agent
// behaviour rather than our code*: earlier runs committed only because
// an agent had happened to run `git config user.email` in the
// checkout, leaving a local identity the server then inherited. Config
// the agent may or may not have written is not a dependency delivery
// can hold, so the identity is supplied here on every call.
//
// Environment rather than `-c`, because these override config without
// needing a leaked string per invocation, and because they name the
// committer as the pipeline — which is the truth. The agents' own
// commits keep whatever identity they set.
.env("GIT_AUTHOR_NAME", COMMIT_IDENTITY_NAME)
.env("GIT_AUTHOR_EMAIL", COMMIT_IDENTITY_EMAIL)
.env("GIT_COMMITTER_NAME", COMMIT_IDENTITY_NAME)
.env("GIT_COMMITTER_EMAIL", COMMIT_IDENTITY_EMAIL)
.output()
.await
.map_err(|e| format!("spawn git: {e}"))?;