fix(missions): stop agents having to work around git ownership
ci / gates (push) Failing after 9s
ci / rust (push) Skipped
ci / frontend (push) Skipped
ci / e2e (push) Skipped
ci / publish (push) Skipped

The captured diff from mission 019fc3ba contained the deliverable and, beside
it, a file the agent had invented:

    +++ b/.gitconfig_temp
    +[safe]
    +	directory = /mission/repo

The server clones as uid 65532 and the mission container runs as root, so
every `git` an agent runs is refused with "detected dubious ownership". Agents
do not surface that as a failure — they improvise around it, and the
improvisation lands in the repository. Left alone it would have been committed
and pushed to the user's repo alongside the real work.

The judge got `GIT_CONFIG_*` for this in dd8dad2; the mission containers never
did. They do now — git's environment form of `-c`, inherited by subprocesses,
so it covers the agent's own git, the `git_operations` tool, and anything that
shells out. Scoped to the checkout, never `--global`.

`.gitconfig_temp` is also added to the capture exclusions. The cause is fixed,
but a stray workaround from some future agent should not reach a user's
repository, and the exclusion costs nothing.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
Omar Sobh
2026-08-02 12:11:36 -07:00
co-authored by Claude Opus 5
parent 3a383aede6
commit a0e6b16abc
2 changed files with 37 additions and 3 deletions
+19 -3
View File
@@ -52,6 +52,13 @@ const EXCLUDED_PATHS: &[&str] = &[
".pytest_cache", ".pytest_cache",
".mypy_cache", ".mypy_cache",
"vendor", "vendor",
// Agent workaround debris. `.gitconfig_temp` appeared on mission 019fc3ba
// when an agent hit git's ownership check and wrote its own safe.directory
// config into the repository root. The cause is fixed (mission containers
// now carry GIT_CONFIG_* env), but excluding the artefact keeps a stray
// workaround out of a user's repository if an agent invents another one.
".gitconfig_temp",
".gitconfig.tmp",
]; ];
/// Cap on the captured patch. Past this the diff is truncated with a marker /// Cap on the captured patch. Past this the diff is truncated with a marker
@@ -397,11 +404,20 @@ mod tests {
assert_eq!(parse_diffstat("\n"), (0, 0, 0)); assert_eq!(parse_diffstat("\n"), (0, 0, 0));
} }
/// Build output must never reach a patch. A phase that ran `cargo build` /// Build output and agent workaround debris must never reach a patch. A
/// leaves a `target/` bigger than the repository. /// phase that ran `cargo build` leaves a `target/` bigger than the
/// repository, and an agent that fought git's ownership check left a
/// `.gitconfig_temp` beside the real work.
#[test] #[test]
fn build_output_is_excluded() { fn build_output_is_excluded() {
for p in ["target", "node_modules", ".venv", "dist", "__pycache__"] { for p in [
"target",
"node_modules",
".venv",
"dist",
"__pycache__",
".gitconfig_temp",
] {
assert!( assert!(
EXCLUDED_PATHS.contains(&p), EXCLUDED_PATHS.contains(&p),
"{p} must be excluded from capture" "{p} must be excluded from capture"
+18
View File
@@ -285,6 +285,24 @@ impl MissionRuntimeProvisioner {
let mut env = vec![ let mut env = vec![
format!("ZEROCLAW_GATEWAY_PORT={GATEWAY_PORT}"), format!("ZEROCLAW_GATEWAY_PORT={GATEWAY_PORT}"),
format!("CM_MISSION_ID={mission_id}"), format!("CM_MISSION_ID={mission_id}"),
// Let the agents' git read the checkout.
//
// The server clones as uid 65532; this container runs as root, so
// every `git` an agent runs hits "detected dubious ownership" and
// refuses the repository. Agents do not report that as a failure —
// they improvise. On mission 019fc3ba one wrote a `.gitconfig_temp`
// containing `[safe] directory = /mission/repo` into the repository
// root, which then showed up in the captured diff and would have
// been committed and pushed to the user's repo alongside the real
// work.
//
// `GIT_CONFIG_*` is git's environment form of `-c` and is
// inherited by subprocesses, so it covers the agent's own git, any
// tool that shells out to git, and the `git_operations` tool alike.
// Scoped to the checkout; never `--global`.
"GIT_CONFIG_COUNT=1".to_string(),
"GIT_CONFIG_KEY_0=safe.directory".to_string(),
"GIT_CONFIG_VALUE_0=/mission/repo".to_string(),
]; ];
// Provider credentials forwarded into the container. // Provider credentials forwarded into the container.
// //