Skip to content

Issue #173 - #182

Merged
immuhammad merged 1 commit into
mainfrom
feature/issue-173
Aug 3, 2026
Merged

immuhammad merged 1 commit into
mainfrom
feature/issue-173

Conversation

@immuhammad

@immuhammad immuhammad commented Aug 3, 2026 •

Copy link
Copy Markdown
Owner

Summary

Four independent hardening fixes from the agy (Reviewer)-lane incident
report: a claimed-but-unposted APPROVE, a token-repetition hang
misclassified after the fact, and stray scratch files left in the
project root.

  1. Verdict-ordering invariant. AGENTS.md gains a relay rule (no
    lane relays a verdict it can't itself see posted on the PR) and an
    explicit Gate-2 checklist step (verify the verdict is present ON the
    PR via gh pr view --json comments,reviews before gh pr ready).
    review-protocol.md makes publishing to the PR a precondition of
    reporting the verdict anywhere else (ack/dispatch/pane summary).

  2. Stuck-detector shape classification (lib/watch.sh
    pane_stuck_check). Previously classified purely on screen-hash
    diff, which is anti-correlated with the real failure: a repetition
    loop's screen changes constantly (stays silent through the actual
    incident) while a post-interrupt idle screen freezes (gets flagged
    after the fact, once the danger has passed). Now three shapes:

    • idle-prompt shape (reusing dispatch.sh's own hook-independent
      screen-scrape heuristic, split out as pane_tail_is_idle_shape) →
      noted to orchestra as probable stale-BUSY (pane_is_idle residual false-BUSY: woz:code spinner line persists after completion, matches #104's shape-anchored busy markers #105's residual
      false-BUSY class), never flagged stuck.
    • runaway-output shape (new wsc_tail_is_runaway: line-repetition or
      whitespace-stripped token-repetition) → flagged immediately, no
      baseline wait (a hash comparison would never see "unchanged" here).
    • unchanged screen → still looping/stuck, now reached only for
      panes genuinely not at a prompt.
      6 new canned-shape tests in tests/watch.test.sh, including a
      legitimately-repetitive-but-distinct-lines case to probe the
      false-positive risk on test logs/progress bars.
  3. agy scratch convention. templates/GEMINI.md (+ live GEMINI.md)
    documents .harness/scratch/agy/ for temporary review artifacts,
    mirroring the Claude lanes' scratch-dir discipline
    (souls/scribe.md). .gitignore gains pr*.diff/pr*_body.txt so
    stray review scratch can't ride a later commit even before agy
    adopts the convention.

  4. Quota-gate coverage audit (lib/gatekeeper.sh). Traced
    budgets.agy.failsafe_pct end to end: the config read was already
    correctly wired (fallback + loud warning, same as Claude's), but the
    standalone "agy over threshold" branch (Claude's own pool still
    under threshold) only ever called gk_alert_orchestra + gk_mark_alerted
    — it never wrote the shared state/quota-stop flag every OTHER
    threshold branch (Claude 5h, weekly, both) already writes. That flag
    is the actual enforcement mechanism both hooks/quota-stop-gate.sh
    (Claude) and lib/guard-quota-stop-agy.sh (agy) gate on — so agy
    crossing its OWN failsafe_pct alone produced an advisory FLAG and
    nothing else: no PreToolUse enforcement, unlike every other pool.
    Fixed with the same ar_write_quota_stop_flag call the other
    branches make (no resets_at — agy's pool has no epoch this codebase
    can read, so it behaves like a "weekly" flag: human-clear-only via
    lib/quota-stop-clear.sh, never silently auto-lifted).
    gatekeeper.sh's main loop had no prior unit-test seam at all (only
    gatekeeper-supervisor.test.sh's crash-loop respawn wrapper existed);
    added tests/gatekeeper-quota-gate.test.sh using the same
    background+sleep+kill pattern, with fetch_claude_usage stubbed so
    it never touches the real Keychain or makes a live network call.

Test plan

  • bash -n on every bin/*, lib/*.sh, hooks/*.sh (CI's syntax step)
  • All 33 tests/*.test.sh suites green (CI's suite, orc-worktree.test.sh
    excluded per its own live-GitHub-cycle header) — includes the 6 new
    shape-classification tests and the new 6-test
    gatekeeper-quota-gate.test.sh
  • bin/orc-install-skills scratch-project smoke test (CI's third step)
  • Reproduced the quota-gate gap live before fixing it (agy-alone-over
    threshold test failed against the pre-fix gatekeeper.sh, confirmed
    passing after)

Closes #173.

…ication, agy scratch, quota-gate coverage (#173)

Four independent fixes from the agy-lane incident report (claimed-unposted
APPROVE, repetition-loop hang, stray root scratch files):

1. Verdict-ordering invariant: AGENTS.md gains a relay rule (no lane
   relays a verdict it can't see on the PR) and an explicit Gate-2
   checklist step (verify the verdict is ON the PR before gh pr ready);
   review-protocol.md makes publish-to-PR a precondition of reporting the
   verdict anywhere.

2. lib/watch.sh pane_stuck_check now classifies by SHAPE, not just a
   screen-hash diff: idle-prompt shape (reusing dispatch.sh's own
   hook-independent heuristic, split into pane_tail_is_idle_shape) is
   noted as probable stale-BUSY (#105) instead of flagged stuck;
   runaway-output shape (new wsc_tail_is_runaway) flags degenerate
   repetition immediately instead of waiting for a hash comparison that
   would never fire; unchanged-screen still means looping/stuck for
   panes not at a prompt. 6 new canned-shape tests.

3. templates/GEMINI.md + live GEMINI.md gain an explicit
   .harness/scratch/agy/ convention, mirroring the Claude lanes'
   scratch-dir discipline. .gitignore gains pr*.diff/pr*_body.txt so
   stray review scratch can't ride a later commit even without agy
   adopting the convention.

4. Quota-gate coverage audit (lib/gatekeeper.sh): budgets.agy.failsafe_pct
   was read correctly but the standalone "agy over threshold" branch
   never wrote the shared quota-stop flag every OTHER threshold branch
   writes -- agy could burn quota past its own failsafe_pct with only an
   advisory FLAG, no actual PreToolUse enforcement. Fixed with the same
   ar_write_quota_stop_flag call the other branches already make (no
   resets_at -- human-clear-only, same as weekly). New
   tests/gatekeeper-quota-gate.test.sh (gatekeeper.sh's main loop had no
   prior unit-test seam at all).

Closes #173.
@immuhammad

Copy link
Copy Markdown
Owner Author

APPROVE

Probe List (Security Pass):

  1. lib/gatekeeper.sh quota-gate coverage: Checked the new ar_write_quota_stop_flag call. It correctly passes "agy", $AGY_PCT, and "claude" (matching the alert text's fallback instruction). It rightly omits the epoch parameter, ensuring agy quota-stop requires manual clearing (same as weekly) since the CLI doesn't expose a usable reset epoch. The flag is written strictly within the over-threshold branch. Perfect.
  2. lib/dispatch.sh pane_tail_is_idle_shape split: Checked the refactor. The regexes and echo "$tail" | grep -Eiq "$markers" execution were extracted exactly as-is into pane_tail_is_idle_shape. pane_is_idle passes both $tail and $agent correctly to the new function, ensuring zero change to live nudge behavior. It is a completely transparent refactor for existing callers.
  3. lib/watch.sh shape heuristics: Verified wsc_tail_is_runaway. The line-duplication check safely uses sort | uniq -c and the token-repetition check relies solely on Bash substring slicing (${squashed:0:$chunk_len}). No eval, no complex regex backreferences (avoiding BSD awk quirks). The 70% squash threshold and distinct-line test cases provide robust defense against false positives (like test runner output).
  4. Docs/Rules: Verified review-protocol.md explicitly demands PR posting before relaying, and GEMINI.md / .gitignore correctly establish the agy scratch directory convention.

All requirements from the Gate-1 plan are fully and safely implemented. Good to go.

@immuhammad
immuhammad marked this pull request as ready for review August 3, 2026 15:31
Copilot AI review requested due to automatic review settings August 3, 2026 15:31
@immuhammad
immuhammad merged commit 48ea41b into main Aug 3, 2026
1 check passed
@immuhammad
immuhammad deleted the feature/issue-173 branch August 3, 2026 15:31

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Not ready to approve

The new gatekeeper quota-gate test installs an EXIT trap before initializing SCRATCH under set -u, which can cause the trap to error if the script exits early or is interrupted.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Pull request overview

Hardening changes addressing incident learnings from issue #173: enforce “verdict must exist on PR before relaying,” improve pane stuck detection to catch runaway repetition earlier (and avoid false stucks when a pane is actually idle), prevent scratch artifacts from landing in repo root, and ensure agy quota thresholds trigger the same enforcement mechanism as other pools.

Changes:

  • Document and enforce verdict-ordering/verification rules in reviewer + Gate-2 protocols.
  • Add shape-based stuck classification (idle-prompt note vs runaway-output immediate flag vs unchanged-screen stuck) with new tests.
  • Enforce agy quota-stop via shared quota-stop flag; add a focused gatekeeper main-loop test; ignore common review scratch artifacts via .gitignore.
File summaries
File Description
tests/watch.test.sh Adds new stuck-shape classification tests (idle-prompt note, runaway-output immediate flag, false-positive probe).
tests/gatekeeper-quota-gate.test.sh New regression test ensuring agy-only threshold crossing writes the shared quota-stop enforcement flag.
templates/review-protocol.md Documents “publish verdict to PR before reporting it elsewhere” invariant.
templates/GEMINI.md Documents agy scratch directory convention under .harness/scratch/agy/.
templates/AGENTS.md Adds verdict relay rule and Gate-2 checklist step to verify verdict exists on PR before gh pr ready.
lib/watch.sh Implements stuck “shape” classification including runaway repetition detection and idle-prompt note behavior.
lib/gatekeeper.sh Writes quota-stop flag for agy threshold crossings (previously only alerted).
lib/dispatch.sh Extracts pane_tail_is_idle_shape helper for reuse by watch stuck-check logic.
.gitignore Ignores common PR diff/body scratch captures to prevent accidental commits.
Review details
  • Files reviewed: 8/9 changed files
  • Comments generated: 1
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Comment on lines +33 to +40
GK_PID=""
cleanup() {
[ -n "$GK_PID" ] && kill "$GK_PID" >/dev/null 2>&1
rm -rf "$SCRATCH"
}
trap cleanup EXIT

SCRATCH="$(mktemp -d)"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reviewer lane (agy): claimed an APPROVE it never posted, plus repetition-loop hang and scratch files in workspace root

2 participants