fix(missions): let the server and the agent share one git checkout
Mission 019fc437 lost both phases' work to:
git add → exit 128: insufficient permission for adding an object
to repository database .git/objects
cm-api runs as uid 65532; the mission runtime container runs as root;
they share one bind-mounted checkout. Git's .git/objects/xx/ fan-out
directories inherit the ownership of whoever creates them, so an agent
that writes objects first locks the server out of those directories.
The failure is intermittent, which is why the previous run looked clean.
Mission 019fc42b's agents committed their own work, so the blobs already
existed and the server's `git add` never had to write one. Same template,
different agent behaviour, opposite outcome.
`core.sharedRepository` is git's own mechanism for this: objects and refs
are created group- and world-writable, and both parties read the setting
from the shared .git/config. It grants the agent nothing — it is already
root over the whole checkout — and unblocks the server, which was the
party being refused. Applied on clone and on checkout reuse.
Two supporting changes. The artifact now records `commit_error`: this
failure surfaced as `branch: null, push_error: null`, indistinguishable
from a phase that never had work to commit, with the reason only in host
stderr. And the test seeder now calls the production setup function
instead of reimplementing it — building the checkout by hand is what let
a clone-path defect stay invisible to fourteen tests.
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
8bad869248
commit
5b53705c97
@@ -236,6 +236,12 @@ pub async fn capture_phase_diff_at(
|
||||
// Commit only after the patch is safely on disk. If this fails, the work
|
||||
// is still captured and the artifact still lands — the branch is the
|
||||
// convenience, the patch is the guarantee.
|
||||
// Why a phase has no branch belongs in the artifact, not only in the log.
|
||||
// Mission `019fc437` recorded `branch: null, push_error: null` for both
|
||||
// phases — indistinguishable from a phase that was never eligible to
|
||||
// commit. The reason was in stderr on the host, where nothing reading the
|
||||
// mission would find it.
|
||||
let mut commit_error: Option<String> = None;
|
||||
let committed = match commit_phase_work(&repo, mission_id, phase_id, iteration).await {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
@@ -243,6 +249,7 @@ pub async fn capture_phase_diff_at(
|
||||
"mission_delivery: mission {mission_id} phase {phase_id} captured but not \
|
||||
committed: {e}"
|
||||
);
|
||||
commit_error = Some(e.chars().take(500).collect());
|
||||
None
|
||||
}
|
||||
};
|
||||
@@ -298,6 +305,7 @@ pub async fn capture_phase_diff_at(
|
||||
"tests_verified": verified,
|
||||
"pushed": published.as_ref().map(|p| p.pushed),
|
||||
"push_error": published.as_ref().and_then(|p| p.error.clone()),
|
||||
"commit_error": commit_error,
|
||||
"files_changed": files_changed,
|
||||
"insertions": insertions,
|
||||
"deletions": deletions,
|
||||
|
||||
Reference in New Issue
Block a user