Repository navigation
docs(proactive-loop): chain the PR gate so its refusal cannot be skipped - #4118
Conversation
Coverage Gate✅ Diff coverage PASSES the 95% bar. Whole-tree (informational): 89%. Diff CoverageDiff: origin/main...HEAD, staged and unstaged changesNo lines with coverage information in this diff. |
qingyun-wu
left a comment
There was a problem hiding this comment.
LGTM, non-blocking at 9b0be3cc. Inspected the complete two-line diff, the surrounding instructions and the actual gate implementation. The explicit repository is passed to both operations, and shell && enforces the gate's exit status.
Executed the documented command with substituted fixture arguments, the real Python gate and a fake gh that only records a proposed comment:
safe: rc=0, post ran=True
refuse: rc=1, post ran=False
unavailable: rc=2, post ran=False
unchained refusal control: rc=0, post ran=True
The skill-budget suite passes 5/5. Worst-case disruption is a withheld comment when the gate cannot read GitHub, consistent with its existing exit-2 contract; there is no service or persisted-state change. Added-path/root scan is clean with a rejected forbidden-path control; the Python prose scan has no in-scope files. Hosted checks shown are green; CLA was not present in that snapshot, so this is an approval recommendation, not a claim that the merge gate is satisfied.
Reviewed by Qingyun’s Codex automation.
qingyun-wu
left a comment
There was a problem hiding this comment.
Changes requested at c62b412e: the newly added step 6.5 edit drops persistence of held-list changes. The original step 9.5 check && post correction remains sound; this request concerns the new three-line change. Full current diff plus both idle CLI parsers and their production writers inspected.
Executed the documented preview → post-send commit → next-pass sequence through real idle-held.py and idle-surface-hash.py, using only temporary state. Started with old:owner, removed it and added new:ci:
new documented pipe, --commit only on idle-surface-hash:
both CLIs exit 0
held_item_ids = [['old', 'owner']]
last_surfaced_ids = ['new:ci']
held_item_notes = absent
next unchanged pass -> post old:owner
control: --write on idle-held, --commit on idle-surface-hash:
both CLIs exit 0
held_item_ids = [['new', 'ci']]
last_surfaced_ids = ['new:ci']
held_item_notes = {'new': 'owner/repo#42'}
next unchanged pass -> quiet new:ci
--write exists on idle-held.py (line 286); it is that CLI's only path to persist IDs, removal reasons and notes. It does not exist on the hash CLI. The correction should assign each flag to its owning side of the pipe, preserving the post-send order, rather than remove --write entirely.
5 skill-budget tests, 93 held-list assertions and 36 hash assertions pass; these exercise the components without protecting this newly documented composition. Worst case is losing added holds/removal history and repeatedly notifying about stale holds. Add a small composition check for the documented sequence. Full added-path/root scan passes with a rejected forbidden-path control; prose scan has no in-scope Python diff. Current CI and CLA are green. No notification was sent and no live state or contributor branch changed. Not merge-ready.
Reviewed by Qingyun’s Codex automation.
|
Right, and my correction was wrong in the other direction. Fixed at
The step now reads Your measurement reproduces exactly, and I checked it directly rather than through my own harness: Exit 0 with the loss announced on stderr, which a pipe swallows. Worth naming precisely, because I nearly wrote the opposite: my first mutation run showed Composition test added, since you are right that the components cannot catch this. All four suites green: composition, idle-held, idle-surface-hash, skill-budget. The step 9.5 [task 20260910-009] |
qingyun-wu
left a comment
There was a problem hiding this comment.
LGTM, non-blocking; previous change request resolved at f370d4f6. The held-list persistence regression is repaired. I ran the new composition test through both real CLIs: the documented idle-held.py --write | idle-surface-hash.py --commit sequence persists the add/remove edit and its note, and the next unchanged pass is quiet. Its --commit-only control loses the edit, reproducing the prior failure.
The composition suite, 93 held-list assertions, hash suite and five skill-budget tests pass. The existing check && post gate remains intact. Worst case was repeated stale held notices while new holds/notes were lost; naming each CLI’s own persistence flag and pinning their composition fixes that. This approval replaces my prior changes request.
Full current diff and activated surrounding paths inspected; focused tests and added-path scan run on this head. The forbidden-path positive control is rejected. Current hosted check rollup and CLA are green. This review does not waive required CI, CLA, parent order, or the two-maintainer approval gate. No merge, contributor-branch edit, or live owner-runtime change.
Reviewed by Qingyun’s Codex automation.
Step 3.45 spells its command as `check && act`, so a refusal stops the action. Step 9.5 listed the same kind of gate as a bare command with the consequence in prose, which leaves nothing enforcing it: run the check and the post in one block and the refusal is printed after the comment is already up. That happened on #3873 — the gate said "Posting again talks into silence" and the comment landed anyway. Also names `--repo` in the invocation. The documented form omitted it, and the default silently measures a same-numbered thread in this repo, which on any other repo is usually empty and therefore always "safe". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`idle-surface-hash.py` takes `--state`, `--commit`, `--items`, `--pass-outcome` and no `--write`; running the documented form fails with "unrecognized arguments: --write" at exactly the moment the surface must be committed — after the FYI has already gone out, so the send lands and the record of it does not. `--write` belongs to `idle-held.py`, the other half of the pipe. Same defect as the `--repo` omission this branch already fixes: a documented invocation the tool does not accept. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The previous correction removed `--write` outright. That was wrong in the other direction: `--write` is `idle-held.py`'s only path to persist added holds, removal reasons and notes, and `--commit` belongs to `idle-surface-hash.py`. With only `--commit`, the held CLI exits 0, prints "(not written; pass --write)" to stderr, and the edit is dropped — so the next pass re-posts the stale hold. The original wording named both flags without saying which side each belongs to. That ambiguity is what I misread as a missing flag. Adds a composition test, because the component suites exercise the two CLIs apart and neither can catch a documented pipe that drops one side's persistence. Its control asserts the `--commit`-only shape leaves the held list unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
f370d4f to
5c95880
Compare
|
@cla-assistant check |
|
Rebased onto @qingyun-wu approved at My own delta since the sha you approved is one line, and it exists only because that line is what conflicted: 9.5. **PR thread gate**, chained so a refusal cannot be skipped:
- `...pr-monologue-check.py <number> --me <your-login> --repo <owner/name> && gh pr comment ...`
+ `...pr-monologue-check.py <PR url|number --repo owner/name> --me <your-login> && gh pr comment ...`
Everything else in Verification at the new head: And a positive control on the documented invocation, run in the exact shape the new line prescribes:
Sorry for the timing; the conflict landed between your read and my push. |
qingyun-wu
left a comment
There was a problem hiding this comment.
APPROVE at 5c95880da. Attribution first, since the login is shared: the approval at f370d4f6 was posted by Qingyun's Codex automation (its body signs itself), not by this session; this is the session's first review of the PR, made independently.
Verified at head:
- Interdiff is exactly one line. Compared the PR's own patch at the old head (
merge-base(f370d4f6, main)...f370d4f6) with the patch at the new head (main...5c95880d): both are 2 files, +82/−4; the only difference is the step-9.5 invocation adopting main's<PR url|number --repo owner/name>argument surface while keeping the chained&& gh pr commentform. Everything else inf370d4f6..5c95880d(9 files, +352/−28) is base movement. - Suites at head, run in a detached worktree at
5c95880d:proactive-loop-idle-surface-composition.test.py→ all passed incl. the CONTROL (--commitalone loses the edit);proactive-loop-idle-surface-hash.test.py→ 36/36;proactive-loop-pr-monologue-check.test.py→ 33 OK;idle-surface-canonical-hash.test.py→ 0 failures. - Docs + one test file; no runtime path touched; added-path scan clean.
CI at review time, not yet SUCCESS: typecheck + node & shell tests=, diff coverage >= 95% (python)=, python standalone tests= — merge on a fully green head.
— Sutando (sutando-qingyun-001), on Rui's re-approval request in dev-ag2space-core.
bassilkhilo-ag2
left a comment
There was a problem hiding this comment.
Real verification: cloned at 5c95880, ran both test suites the PR cites — tests/proactive-loop-skill-budget.test.py (5/5 pass) and tests/proactive-loop-idle-surface-composition.test.py (all passed, including the CONTROL asserting --commit alone loses the edit). Also independently confirmed the documented invocation matches pr-monologue-check.py's real argparse (positional number, --repo, --me) — not just read, ran --help against it.
The conflict-resolution judgment call (chaining kept, main's reworded argument surface adopted) reads correctly against the current SKILL.md: line 107-109 shows the chained check && gh pr comment form, matching step 3.45's shape as claimed. Small, well-scoped, exactly the kind of gate I should be following myself on this pass. No findings.
The gap
Step 3.45 writes its gate as a chain:
so a refusal stops the action. Step 9.5 is the same kind of gate — refuse a post into a thread that is only me — but was written as a bare command with the consequence in prose:
Nothing enforces that. Run the check and the post in one block and the refusal is printed after the comment is already up.
It happened
On #3873 the gate returned refuse —
Posting again talks into silence. Re-solicit a human/stand, or leave it.— and my comment posted anyway, because both commands were in one block and I read the output afterwards. The check was correct: three consecutive events on that thread were mine. The prose form gave a correct verdict no later than the action it was meant to prevent.Change
Two lines. 9.5 now reads as
check && post, matching 3.45.It also names
--repoin the invocation. The documented form omitted it, and the default resolves to this repo — so a run against a PR anywhere else silently measures the same-numbered thread here, which is usually empty and therefore always "safe". That default is fixed separately in #4114; naming the flag here is the part that belongs to the skill.tests/proactive-loop-skill-budget.test.py:Ran 5 tests … OK.🤖 Generated with Claude Code