mm-worktree-task: --gate re-runs the test suite itself, never trusts the agent's claim
Across ~12 supervised MiniMax units, more than half reported a green suite where a manual re-run found red (see memory/gotchas-minimax.md). Add --gate "<command>" (with --gate-timeout, default 900s): after the detached sub-session exits, mm-worktree-task itself re-runs the command in the worktree against the sub-session's LAST commit, in its own reapable process group (setsid + group kill, so a hung gate and everything it forked are bounded and cleaned up), and appends an unambiguous PASSED/FAILED verdict — plus an explicit MISMATCH callout when the agent's own report claimed success — to the result file. A missing command, non-zero exit, timeout, or dirty worktree are all failures, never a silent skip. mm-worktree-task's own exit code is unchanged: the launch already returns before the sub-session (and gate) run, detached. Claude-Session: https://claude.ai/code/session_014KNyfHmHpMbdMrHZXDRkAp
This commit is contained in:
@@ -33,6 +33,12 @@ Options:
|
||||
--docs all|recent|none Which item documents to bundle (default: recent)
|
||||
--tools "<list>" Extra --allowedTools entries (comma-separated)
|
||||
--timeout <secs> Sub-session timeout (default: 1800)
|
||||
--gate "<command>" Re-run this command in the worktree yourself, after
|
||||
the sub-session exits, against its LAST commit.
|
||||
Appended as a "Supervisor gate" section in the
|
||||
result file; a missing command, non-zero exit, or
|
||||
timeout are all reported as FAILED, never skipped.
|
||||
--gate-timeout <secs> Max seconds the --gate command may run (default: 900)
|
||||
--context <name> cp-cli planner context (default: the ~/.cpconfig context
|
||||
whose project_id equals the repo name, else cp-cli's own
|
||||
current_context)
|
||||
@@ -62,6 +68,8 @@ BASE="main"
|
||||
DOCS="recent"
|
||||
EXTRA_TOOLS=""
|
||||
TIMEOUT=1800
|
||||
GATE=""
|
||||
GATE_TIMEOUT=900
|
||||
UPLOAD=0
|
||||
COLLECT=0
|
||||
DRYRUN=0
|
||||
@@ -76,6 +84,8 @@ while [[ $# -gt 0 ]]; do
|
||||
--docs) DOCS="$2"; shift 2 ;;
|
||||
--tools) EXTRA_TOOLS="$2"; shift 2 ;;
|
||||
--timeout) TIMEOUT="$2"; shift 2 ;;
|
||||
--gate) GATE="$2"; shift 2 ;;
|
||||
--gate-timeout) GATE_TIMEOUT="$2"; shift 2 ;;
|
||||
--context) CP_CONTEXT="$2"; shift 2 ;;
|
||||
--upload) UPLOAD=1; shift ;;
|
||||
--collect) COLLECT=1; shift ;;
|
||||
@@ -381,6 +391,8 @@ if [[ $DRYRUN -eq 1 ]]; then
|
||||
echo " Prompt path: ${PROMPT_FILE}"
|
||||
echo " Timeout: ${TIMEOUT}s"
|
||||
echo " Allowlist: ${ALLOWLIST}"
|
||||
echo " Gate: ${GATE:-<none>}"
|
||||
[[ -n "$GATE" ]] && echo " Gate timeout: ${GATE_TIMEOUT}s"
|
||||
echo
|
||||
echo "--- Bundle manifest ---"
|
||||
echo "$BUNDLE_CONTENT"
|
||||
@@ -406,6 +418,93 @@ fi
|
||||
info "creating worktree ${WORKTREE} on branch ${BRANCH}"
|
||||
git -C "$REPO_ROOT" worktree add "$WORKTREE" -b "$BRANCH" "$BASE"
|
||||
|
||||
# --- supervisor gate: re-run --gate ourselves after the sub-session exits,
|
||||
# against its LAST commit. Never trust the agent's own "tests pass" claim —
|
||||
# see memory/gotchas-minimax.md: more than half of supervised units reported
|
||||
# green where a re-run found red. A missing command, non-zero exit, or
|
||||
# timeout are all FAILED, never a silent skip.
|
||||
run_gate() {
|
||||
local gate_sha status_out dirty_flag=0 gate_out_file gate_rc timed_out=0
|
||||
local gate_tail verdict had_report=0 agent_claims_pass=0
|
||||
local gate_pid watchdog_pid timeout_flag
|
||||
|
||||
gate_sha="$(git -C "$WORKTREE" rev-parse HEAD 2>&1)" || gate_sha="<unresolvable HEAD: ${gate_sha}>"
|
||||
status_out="$(git -C "$WORKTREE" status --porcelain 2>&1)"
|
||||
[[ -n "$status_out" ]] && dirty_flag=1
|
||||
|
||||
# Plain `timeout bash -c "$GATE"` only signals the immediate bash wrapper —
|
||||
# any grandchildren the gate command forks (a test runner's own workers,
|
||||
# say) are reparented and keep running unbounded. `setsid` makes the gate
|
||||
# its own process group (pgid == its pid), so the watchdog can signal the
|
||||
# whole group (`-$gate_pid`) and actually reap everything the gate started.
|
||||
gate_out_file="$(mktemp)"
|
||||
timeout_flag="$(mktemp -u)"
|
||||
set +e
|
||||
setsid bash -c "$GATE" > "$gate_out_file" 2>&1 &
|
||||
gate_pid=$!
|
||||
(
|
||||
sleep "$GATE_TIMEOUT"
|
||||
if kill -0 "$gate_pid" 2>/dev/null; then
|
||||
: > "$timeout_flag"
|
||||
kill -TERM -- "-${gate_pid}" 2>/dev/null
|
||||
sleep 5
|
||||
kill -KILL -- "-${gate_pid}" 2>/dev/null
|
||||
fi
|
||||
) &
|
||||
watchdog_pid=$!
|
||||
wait "$gate_pid"
|
||||
gate_rc=$?
|
||||
kill "$watchdog_pid" 2>/dev/null
|
||||
wait "$watchdog_pid" 2>/dev/null
|
||||
set -e
|
||||
[[ -f "$timeout_flag" ]] && timed_out=1
|
||||
rm -f "$timeout_flag"
|
||||
gate_tail="$(tail -n 60 "$gate_out_file" 2>/dev/null)"
|
||||
rm -f "$gate_out_file"
|
||||
|
||||
if [[ $dirty_flag -eq 1 || $gate_rc -ne 0 ]]; then
|
||||
verdict="FAILED"
|
||||
else
|
||||
verdict="PASSED"
|
||||
fi
|
||||
|
||||
[[ -f "$RESULT_FILE" ]] && had_report=1
|
||||
if [[ $had_report -eq 1 ]] \
|
||||
&& grep -qiE '(all tests? pass|0 failed|tests? pass(ed)?|passing)' "$RESULT_FILE" 2>/dev/null \
|
||||
&& ! grep -qiE '[1-9][0-9]* (failed|failing)' "$RESULT_FILE" 2>/dev/null; then
|
||||
agent_claims_pass=1
|
||||
fi
|
||||
|
||||
{
|
||||
echo
|
||||
echo "## Supervisor gate (re-run by mm-worktree-task)"
|
||||
echo
|
||||
[[ $had_report -eq 0 ]] && echo "(No agent report was found at this path before the gate ran.)"
|
||||
echo "- HEAD: ${gate_sha}"
|
||||
echo "- Command: \`${GATE}\`"
|
||||
if [[ $dirty_flag -eq 1 ]]; then
|
||||
echo "- Worktree status: DIRTY — uncommitted changes present; result does not correspond to a fully committed state"
|
||||
else
|
||||
echo "- Worktree status: clean"
|
||||
fi
|
||||
if [[ $timed_out -eq 1 ]]; then
|
||||
echo "- Exit code: ${gate_rc} (TIMED OUT after ${GATE_TIMEOUT}s)"
|
||||
else
|
||||
echo "- Exit code: ${gate_rc}"
|
||||
fi
|
||||
echo "- Verdict: **${verdict}**"
|
||||
if [[ "$verdict" == "FAILED" && $agent_claims_pass -eq 1 ]]; then
|
||||
echo
|
||||
echo "**MISMATCH:** the agent's own report above claims a passing test run; this independent re-run of the gate FAILED. Treat this section as authoritative, not the agent's claim."
|
||||
fi
|
||||
echo
|
||||
echo "Output (tail):"
|
||||
echo '```'
|
||||
echo "$gate_tail"
|
||||
echo '```'
|
||||
} >> "$RESULT_FILE"
|
||||
}
|
||||
|
||||
info "launching ask-minimax-run (detached, timeout ${TIMEOUT}s)"
|
||||
(
|
||||
cd "$WORKTREE"
|
||||
@@ -417,6 +516,9 @@ info "launching ask-minimax-run (detached, timeout ${TIMEOUT}s)"
|
||||
> "$STDOUT_FILE" 2>&1
|
||||
rc=$?
|
||||
set -e
|
||||
if [[ -n "$GATE" ]]; then
|
||||
run_gate
|
||||
fi
|
||||
echo "${SLUG} rc=${rc} elapsed=$(( $(date +%s) - start ))s" >> "$LAUNCH_LOG"
|
||||
) &
|
||||
disown
|
||||
|
||||
Reference in New Issue
Block a user