team: snapshot worktree on submit + refuse no-op submissions #177
Conversation
team agent submit froze the env branch tip, but nothing committed the agent's worktree edits first — so an agent that edited files and submitted without committing froze the base tree, and reviewers saw nothing to review. - env::snapshot_for_submit: locked mediated-commit of the env worktree (mirrors propose); no-op when there is no local worktree (pulled clone). - team::submit auto-snapshots on the no-explicit-commit path, then refuses a submission whose tree is identical to the team base (nothing to review). - tests: submit_refuses_no_op_submission, submit_auto_snapshots_dirty_worktree; updated two existing tests to advance the env branch off base.
…--as - --role was never a CLI flag; replace with --persona <file> (the real working-style mechanism; see examples/personas/). - omit --as from all example commands (the agent id auto-generates); surface the generated ids via h5i team status and reference them in downstream --agent flags. Keep --as in the flag reference, marked optional. - document team submit's new auto-snapshot + no-op refusal; add the missing team persona row. Regenerate docs/manual from MANUAL.md.
Regression from 236c004: snapshot_for_submit acquired the env run lock, but team submits are ingested *while the agent's box is still alive* (the team Stop hook keeps boxes running; team sync drains mid-round), so the box always holds run.lock. The non-blocking acquire (LOCK_EX|LOCK_NB) returned 'busy', team::submit errored, and ingest_team_outbound silently deleted the staged spool — so every mid-round submit was dropped (submits: 0, no events), even for agents that had already committed their work. - snapshot_for_submit is now best-effort: on lock contention it falls back to the branch tip (Ok(None)) instead of failing. A committed worktree records immediately; an uncommitted one is snapshotted by the at-exit ingest once the lock frees. - ingest_team_outbound keeps the spool file on submit failure (retryable at box exit) and records the failure as a durable env event instead of a stderr warning a script-polled sync swallows. - test: snapshot_for_submit_is_best_effort_under_run_lock.
The host can't snapshot a *live* box: the team Stop hook keeps a box alive for the whole round, so it holds the env run lock the entire session, and the at-exit ingest never runs mid-round. An agent that writes files but never commits (codex left quick_sort.py/pyproject.toml as untracked `??` files) therefore staged a no-op the host refused as 'identical to the team base' — freeze blocked, the round wedged. Fix: the in-box `team agent submit` now commits the worktree onto the env branch itself (commit_box_worktree), where the box has a functional checkout (rw on its own branch + objects). The host ingest then freezes the advanced tip — exactly the path a well-behaved agent already takes by committing in-box. Best-effort: any git error degrades to a no-op so submit is never blocked; an explicit --commit skips it. - env::commit_box_worktree / commit_worktree_at (testable, path-based). - main: call it on the in-box no-explicit-commit submit path. - test: commit_box_worktree_snapshots_untracked_edits.
commit_box_worktree returned Option and used .ok()? throughout, so ANY git error inside the box (e.g. a too-narrow box_git_plumbing grant) degraded to a silent no-op — indistinguishable from a clean worktree, and exactly what makes an agent's work vanish into a 'no changes to review' submission. Return Result<Option<Oid>> and have the in-box `team agent submit` print a clear warning on Err (while still staging — a failed snapshot must never block the submit; the host then freezes the branch tip). Next run self-diagnoses: either '▢ snapshotted worktree in-box at <oid>' or a concrete error to act on.
Follow-up to the in-box submit snapshot. Two parts: 1. commit_box_worktree no longer calls git2 index.write() before committing — the proven-working `h5i capture commit` path never persists the index from git2 (the agent's `git add` does), and writing worktrees/<wt>/index via a lock+rename is the step most likely to EACCES in a tight box layout while the object + ref writes succeed. Land the commit from the in-memory index (write_tree + commit), then refresh the index best-effort. Also return Result so a real failure is surfaced (warning) instead of silently becoming a no-op. 2. h5i init guidance (AGENTS.md / .claude/h5i.md templates + this repo's copies): 'In an agent team, always h5i capture commit before h5i team agent submit.' This is the reliable mitigation — a committed worktree submits correctly regardless of whether the auto-snapshot can run (it's what well-behaved agents already do).
🪙 86% AI-authored · 7 files · prompt 51/100
💬 The ask
📍 What shipped
Note 🌿 Prompt maturity: 51/100 · proficient · 5 prompts scored (83% of AI commits) 📊 heuristic breakdown
Readability (display-only): Flesch 51 · FK grade 11.8 · Gunning Fog 14.2 Offline classical-NLP score — no LLM. Higher = more specific, bounded, and grounded prompting. Tip ✅ Security scan cleanNo credentials leaked in this branch's diff. h5i scanned every added line against the secret rule pack and found nothing to rotate. Note Duplicate-code scan clean — no copy-paste blocks introduced. 🧠 Reasoning by file — 9203 nodes across 6 lanes, latest 24 only%%{init: {"flowchart": {"nodeSpacing": 42, "rankSpacing": 48, "diagramPadding": 14}, "themeVariables": {"fontSize": "18px"}} }%%
flowchart TB
subgraph lane_0["💭 Reasoning · 8 steps"]
direction LR
n_lane_0_0["THINK · Let me retry on the `process` tier (which runs for real on this host):"]:::t
n_lane_0_1["THINK · Let me retry the supervised box with `env run` (non-interactive) instead…"]:::t
n_lane_0_2["THINK · Let me refactor `commit_box_worktree` to return a `Result` so the caller…"]:::t
n_lane_0_3["THINK · claude (felix) committed fine in-box, so let me compare how *it* commits…"]:::t
n_lane_0_4["THINK · write()`. Rather than keep reasoning, let me reproduce at the `process` …"]:::t
n_lane_0_5["THINK · md (”always commit before submit”) — I'll do that after this repro confi…"]:::t
n_lane_0_6["NOTE · DEFERRAL (~/Dev/h5i/src/main.rs): m-submit-*.json` is kept by the no-dro…"]:::n
n_lane_0_7["NOTE · PLACEHOLDER (~/Dev/h5i/src/env.rs): My repro profile was incomplete — th…"]:::n
n_lane_0_0 --> n_lane_0_1
n_lane_0_1 --> n_lane_0_2
n_lane_0_2 --> n_lane_0_3
n_lane_0_3 --> n_lane_0_4
n_lane_0_4 --> n_lane_0_5
n_lane_0_5 --> n_lane_0_6
n_lane_0_6 --> n_lane_0_7
end
subgraph lane_1["📄 src/main.rs · 6 ops"]
direction LR
n_lane_1_0["EDIT"]:::a
n_lane_1_1["READ × 2"]:::o
n_lane_1_2["EDIT"]:::a
n_lane_1_3["READ"]:::o
n_lane_1_4["EDIT"]:::a
n_lane_1_0 --> n_lane_1_1
n_lane_1_1 --> n_lane_1_2
n_lane_1_2 --> n_lane_1_3
n_lane_1_3 --> n_lane_1_4
end
subgraph lane_2["📄 src/env.rs · 3 ops"]
direction LR
n_lane_2_0["EDIT"]:::a
n_lane_2_1["READ"]:::o
n_lane_2_2["EDIT"]:::a
n_lane_2_0 --> n_lane_2_1
n_lane_2_1 --> n_lane_2_2
end
subgraph lane_3["📄 AGENTS.md · 2 ops"]
direction LR
n_lane_3_0["READ"]:::o
n_lane_3_1["EDIT"]:::a
n_lane_3_0 --> n_lane_3_1
end
subgraph lane_4["📄 .claude/h5i.md · 2 ops"]
direction LR
n_lane_4_0["READ"]:::o
n_lane_4_1["EDIT"]:::a
n_lane_4_0 --> n_lane_4_1
end
subgraph lane_5["🗂 Other · 3 nodes"]
direction LR
n_lane_5_0["READ · read src/repository.rs"]:::o
n_lane_5_1["READ · read src/sandbox_policy.rs"]:::o
n_lane_5_2["READ · read src/sandbox.rs"]:::o
n_lane_5_0 --> n_lane_5_1
n_lane_5_1 --> n_lane_5_2
end
classDef o fill:#dbeafe,stroke:#1e3a8a,color:#0b1c4a;
classDef t fill:#fef3c7,stroke:#92400e,color:#3f2d05;
classDef a fill:#dcfce7,stroke:#166534,color:#0a2e16;
classDef n fill:#ede9fe,stroke:#5b21b6,color:#221251;
classDef m fill:#e5e7eb,stroke:#374151,color:#0b0f17;
📜 Per-commit provenance — 6 AI-authored commits
|
No description provided.