fix: scope agent gate applicability to real dispatch evidence - #1154
fix: scope agent gate applicability to real dispatch evidence#1154groupthinking wants to merge 1 commit into
Conversation
`agentTaskApplicable` unioned the pull request's labels with the linked issue's labels, so a linked issue carrying `agent-task` made any pull request an agent completion. Label automation applies `agent-task` as a topic tag to issues that were never created from the agent task template and therefore never declared an Agent Run ID or Agent Login. Those pull requests were then measured against a contract that did not exist, producing `blocked` / `invalid_payload` with `policy.agent_login` and `policy.run_id` invalid -- a state no author could reach, because the missing fields live on an issue that was never dispatched. Applicability is now a function of dispatch evidence: - Pull request provenance (known agent author, agent branch prefix, an agent label on the pull request, or a lock manifest in its body) is sufficient on its own, so fail-closed behaviour is preserved for real agent work regardless of how the issue is labelled. - The linked issue's agent label counts only when the issue body actually declares the Agent Run ID and Agent Login the gate goes on to require, ignoring the `_No response_` placeholder left by unfilled issue forms. The contract check is nested inside `agentTaskApplicable` so the function stays self-contained for the workflow's `node -e` extraction harness, and both copies remain byte-identical. A mislabelled issue is now surfaced with `core.notice` as `mislabelled_agent_task`; it cannot be reported as a collection error because the gate short-circuits to `not_applicable` before reading them. Two pre-existing assertions encoded the old behaviour and are updated to the corrected contract while preserving their original intent: the row proving a linked issue's labels are consulted now covers both the contract-less and contract-bearing cases, and the textual-link fallback check keeps its contrast by declaring a contract on the selected issue. Fixes #1130 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are limited based on label configuration. 🏷️ Required labels (at least one) (1)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository YAML (base), Repository UI (inherited), Organization UI (inherited) Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Snapshot WarningsEnsure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice. Scanned FilesNone |
Agent Completion Truth Gate: BLOCKEDReasons: Machine-readable verdict{
"details": {
"collection_errors": [
"incomplete_linked_issue_contract",
"missing_intent_snapshot",
"missing_agent_run_id",
"missing_agent_login"
],
"invalid_fields": [
"policy.agent_login",
"policy.run_id"
]
},
"reasons": [
"invalid_payload"
],
"verdict": "blocked"
} |
There was a problem hiding this comment.
Pull request overview
Refines the agent-completion gate so linked issue labels only trigger it when backed by an agent contract.
Changes:
- Separates PR provenance from issue-side dispatch evidence.
- Emits notices for mislabelled agent-task issues.
- Adds regression and applicability tests.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
.github/workflows/pr-checks.yml |
Updates applicability and diagnostic logic. |
tests/unit/test_agent_completion_gate.py |
Adds contract and provenance test cases. |
Suppressed comments (1)
.github/workflows/pr-checks.yml:2001
carriesAgentLabelalso accepts the genericagentlabel, but issue dispatch labels are limited toagenttask/mcpagentby the snapshot job (lines 54-56) and the collector (lines 2188-2190). An issue withagentplus these headings therefore makes a human PR applicable, only to be permanently blocked aslinked_issue_not_agent_taskand without a snapshot. Restrict this second copy identically so scheduled and per-PR applicability remain aligned.
const issueDispatch = carriesAgentLabel(issueLabelSource) &&
declaresAgentContract(selectedIssue);
| const issueDispatch = carriesAgentLabel(issueLabelSource) && | ||
| declaresAgentContract(selectedIssue); |
| if (!applicable && issue && | ||
| ['agenttask', 'mcpagent'].some(label => issueLabels.has(label))) { |
Independent verification — ready to merge on the test evidenceChecked out this PR's head ( All 106 pass, including the regression cases that pin the corrected contract:
Why
|
|
Automated review — verified locally against head Verdict: correct and ready. This faithfully implements both remediations designed in #1130:
Verification: On the red No code defects found. I'm not merging autonomously — this targets protected Generated by Claude Code |
groupthinking
left a comment
There was a problem hiding this comment.
Automated review — review requested on #1154
Verdict: sound fix, mergeable, with one narrow non-blocking follow-up. Ran the full suite locally at c8106c7:
PYTHONPATH=. python3 -m unittest tests.unit.test_agent_completion_gate → 106/106 pass. The change correctly dissolves the #1130/#1123 deadlock — applicability now follows real dispatch evidence, and a mislabelled agent-task issue surfaces as a core.notice() instead of an unresolvable invalid_payload.
1. Non-blocking — issueDispatch accepts the generic agent label, but the rest of the issue side doesn't (seconds Copilot's suppressed note, verified).
carriesAgentLabel matches ['agent','agenttask','mcpagent'] and is reused for issue-side dispatch (issueDispatch, ≈L698 / ≈L2000). But every other issue-side path is restricted to ['agenttask','mcpagent']: the snapshot trigger (L54–56), linked_issue_not_agent_task (L711–714 / L2188–2190), and the new mislabel notice (L2174). So an issue carrying a bare agent label plus a declared contract goes applicable=true → the collector then sees no agent-task/mcp-agent label → pushes linked_issue_not_agent_task → the PR blocks. That's a narrower recurrence of the exact #1130 deadlock this PR removes. Verified against the head workflow's own agentTaskApplicable:
true <= issue generic `agent` label + contract (edge: becomes applicable, then blocks downstream)
true <= issue `agent-task` label + contract (intended)
false <= issue `bug` label + contract (control)
Fix: gate issueDispatch on the restricted set (only agenttask/mcpagent), keeping the generic agent label as PR-side provenance only. Both copies (≈L698 and ≈L2000) must change identically — test_agent_applicability_copies_stay_identical will enforce that.
2. Merge-process — this PR structurally cannot green its own agent-completion/truth-gate; that red is expected, not a defect.
pr-checks.yml (which emits the truth-gate status) triggers on pull_request_target / workflow_run / schedule — all of which run the workflow definition from main, never the PR head. So the failing agent-completion/truth-gate (and the related Agent completion enforcement / Canonical issue and evidence / PR Governance) are executing main's old buggy applicability logic; this diff only takes effect once merged. Requiring these to go green before merging is the same self-referential trap the PR fixes, one level up. Recommend an admin/bypass merge rather than waiting for them. Separately, the red gitleaks (working tree) is the unrelated repo-wide uv.lock checksum false-positive (open fix in #1141/#1142) — this diff touches only pr-checks.yml and one test file, so it isn't implicated.
I have not merged (protected main, unattended scheduled run) and not pushed to the branch (not my designated branch — no authorization to write here). Once you accept #2, the staged action is a squash-merge of #1154; #1 can land as a small follow-up.
Generated by Claude Code
Automated triage — check status consolidatedRan the remediation loop against head Substantive checks all green — The reds are all non-blocking / expected:
Net: I have not merged or pushed: I'm limited to my own working branch and won't merge to protected Generated by Claude Code |
|
Flagging overlap so these get sequenced rather than colliding: this and #1151 both target the Agent Lock gate, but on different axes and different files, so they're complementary rather than competing.
Root cause for #1151, for context: the workflow greps head-SHA check runs for a check named No conflict between the two — the +167 lines of tests here are a genuine improvement over the current coverage. Both can land in either order. |
The truth gate scores a pull request against the frozen intent snapshot on its linked issue. That snapshot is only ever written by snapshot-agent-task-intent, which runs on `issues` events alone -- it never runs on pull_request_target. So a pull request with no linked issue has no snapshot, no declared agent_login and no declared run_id, which means policy.agent_login, policy.run_id and issue.number can never be populated and the verdict is permanently `invalid_payload` no matter what the author does. Arming that unsatisfiable state from a branch-name prefix made the check red on pull requests that never had a contract to satisfy. It is red on merged commits too: #1368, the current tip of main, merged with agent-completion/truth-gate/pr-1368 failing on exactly this. A check that is red on everything gates nothing and buries real failures, which is the failure mode agent-completion-enforcement.yml already warns about in its own comments. Pull-side provenance now arms the gate only when a linked issue exists to verify against. With none there is nothing to measure, so the verdict is not_applicable rather than blocked. This is not an escape hatch: a pull request that links a dispatched issue is gated exactly as before, and the requirement to bind a pull request to a focused issue at all is separately owned by the `Canonical issue and evidence` check, which states a requirement an author can actually meet. Consolidates the two competing open implementations of this fix. #1364's commit is cherry-picked here with authorship intact; #1154 carried the same intent but had drifted to 117 files and 14k lines of unrelated changes. Both should close in favour of this. Full unit suite: 8079 passed, 0 failed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YcHjCZ6pGn6A5BeeoZ6eZi
Closing in favour of #1377This PR and #1364 were competing implementations of the same fix — scoping truth-gate applicability to real dispatch evidence. Measured against current
The focused commit here ( #1364 implemented the same idea cleanly, and its commit is now cherry-picked into #1377, which additionally requires a linked issue before pull-side provenance arms the gate — completing the "real dispatch evidence" principle this PR set out to establish. Closing this; #1377 is canonical. Branch retained and will be archive-tagged before any deletion. Generated by Claude Code |
|
Correction to the numbers above. The close stands, but I gave the wrong mechanism. The "117 files / +14,266" figure came from a diff against an empty merge base, not a real measurement. The actual state: This branch shares no ancestry with current So the comparison I drew with #1364 was not really "focused vs. bloated" — it was "rooted in current Disposition unchanged: #1377 is canonical. Generated by Claude Code |
… audit 337 branches (#1377) * test: stop asserting a CPython-version-specific rmtree detail test_cleanup_is_total_for_non_oserror_failures failed on Python 3.11.15: its premise asserted that shutil.rmtree(path, ignore_errors=True) raises ValueError on a NUL-byte path. That is an implementation detail which has changed -- ignore_errors now absorbs the non-OSError as well, so the assertion no longer holds. The contract under test is unaffected: _cleanup_download_artifacts must swallow non-OSError failures because it runs from a finally block and would otherwise replace the in-flight exception. Establish that premise against the unguarded rmtree call, which still raises, so the test proves the helper's own defensiveness rather than the stdlib's. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YcHjCZ6pGn6A5BeeoZ6eZi * fix(ci): stop arming truth gate from bare agent labels on linked issues agentTaskApplicable() unioned PR labels with linked-issue labels, so a bare agent-task/mcp-agent label applied by label automation to an issue that never declared a contract judged any closing PR an agent completion. The gate then required an Agent Run ID / Agent Login the issue never declared, yielding a permanent blocked/invalid_payload verdict no author could satisfy. Applicability now comes from PR provenance (known agent author, agent branch prefix, agent label on the PR, lock manifest) or from a genuine issue-side dispatch: an agent-task/mcp-agent label AND declared Agent Run ID + Agent Login headings. The generic agent label remains a PR-side signal only, since the snapshot job and collector never recognise it issue-side — arming from it would block permanently as linked_issue_not_agent_task with no snapshot to satisfy. The collector now emits a mislabelled_agent_task core.notice when a linked issue carries a contract label without declaring the contract, keyed on the missing contract itself (not inapplicability) so Dependabot PRs linked to valid contracts never get a false notice. Generated with [Linear](https://linear.app/myxstack/issue/GRV-196/agent-completiontruth-gate-is-permanently-unsatisfiable-for-any-pr#agent-session-8e5d62f0) Co-authored-by: linear-code[bot] <222613912+linear-code[bot]@users.noreply.github.com> * fix(ci): require a linked issue before arming the truth gate The truth gate scores a pull request against the frozen intent snapshot on its linked issue. That snapshot is only ever written by snapshot-agent-task-intent, which runs on `issues` events alone -- it never runs on pull_request_target. So a pull request with no linked issue has no snapshot, no declared agent_login and no declared run_id, which means policy.agent_login, policy.run_id and issue.number can never be populated and the verdict is permanently `invalid_payload` no matter what the author does. Arming that unsatisfiable state from a branch-name prefix made the check red on pull requests that never had a contract to satisfy. It is red on merged commits too: #1368, the current tip of main, merged with agent-completion/truth-gate/pr-1368 failing on exactly this. A check that is red on everything gates nothing and buries real failures, which is the failure mode agent-completion-enforcement.yml already warns about in its own comments. Pull-side provenance now arms the gate only when a linked issue exists to verify against. With none there is nothing to measure, so the verdict is not_applicable rather than blocked. This is not an escape hatch: a pull request that links a dispatched issue is gated exactly as before, and the requirement to bind a pull request to a focused issue at all is separately owned by the `Canonical issue and evidence` check, which states a requirement an author can actually meet. Consolidates the two competing open implementations of this fix. #1364's commit is cherry-picked here with authorship intact; #1154 carried the same intent but had drifted to 117 files and 14k lines of unrelated changes. Both should close in favour of this. Full unit suite: 8079 passed, 0 failed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YcHjCZ6pGn6A5BeeoZ6eZi * chore(maintenance): audit all 337 remote branches and add a prune script Classifies every remote branch using the one signal that stays honest after the secret-purge force-push: whether it shares any ancestry with `main`. $ git merge-base origin/main origin/<branch> (empty) 275 of 337 branches return empty -- they predate the rewrite and no rebase recovers them. The signals the branch-cleanup harness normally leans on all mislead here, so they are deliberately not used: * `git merge-tree` calls these orphans a CLEAN merge; unrelated trees do not textually conflict, they would clobber. * A two-dot diff against an empty merge base silently degrades to a working-tree diff, which is why a two-line Dependabot bump measures as 111 files / 15,650 lines. * The purge rewrote committer dates, so every branch reads as under 30 days old and no staleness threshold ever fires. Running the stock harness on this repo produced 263 REVIEW off those bad signals. The ancestry test resolves the same set into: KEEP-OPEN-PR 28 REVIEW-SHARED 29 real shared ancestry, no open PR -- not pruned CLOSE-MERGED 1 tip is an ancestor of main CLOSE-ORPHANED 275 The script prunes only the last two groups (276 branches) and archive-tags each one first, verifying every tag is on the remote before deleting anything. It defaults to a dry run. That dry run caught the audit classifying `main` itself as CLOSE-MERGED -- `git merge-base --is-ancestor origin/main origin/main` is trivially true. The row is removed, and a protected-ref guard plus a default-branch check now abort regardless of what the CSV contains. Not executed here: this session's credentials are scoped to one branch and tag creation fails with HTTP 403, so the archive tags cannot be written. Deleting without them would remove the only durable recovery path, so nothing was deleted. Run the script with tag-write credentials. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YcHjCZ6pGn6A5BeeoZ6eZi * docs(maintenance): correct prune-script header counts to match the CSV The header summary read "CLOSE-MERGED 2 / 277 branches" while the shipped docs/branch-audit-2026-08-05.csv has 1 CLOSE-MERGED and 276 prunable. The header was written before the `main` row was dropped from the CSV and was never updated. Comment only -- the selection logic already read from the CSV, so the script was correctly selecting 276 the whole time. Verified: header, CSV tally, and the script's own dry-run count now all agree at 276. Reported by the Vercel review bot on #1377. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YcHjCZ6pGn6A5BeeoZ6eZi --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: linear-code[bot] <222613912+linear-code[bot]@users.noreply.github.com>
Canonical issue
Fixes #1130
Outcome
agent-completion/truth-gatestops issuing verdicts that no author can act on.Today the check is permanently unsatisfiable for any PR whose linked issue
carries the
agent-tasklabel without having declared the agent contract.agentTaskApplicable(pull, selectedIssue)in.github/workflows/pr-checks.ymlunions the PR's labels with the linked issue's labels.
linear-code[bot]applies
agent-taskto[aw]issues as a topic tag; those issues were nevercreated from
.github/ISSUE_TEMPLATE/agent-task.yml, so they declare noAgent Run IDand noAgent Login.Any PR closing such an issue is therefore judged an agent completion and then
required to satisfy a contract the issue never declared:
Observed on #1123. There is no honest escape: fabricating a run ID, or adding an
agent-lock-manifestto a human PR, defeats precisely what the gate protects.After this change, applicability is a function of dispatch evidence rather
than of an incidental label, and a mislabelled issue surfaces as an actionable
mislabelled_agent_tasknotice instead of an unresolvableinvalid_payload.Scope
agentTaskApplicablein.github/workflows/pr-checks.yml(bothbyte-identical copies), the new
mislabelled_agent_taskdiagnostic, and thetests covering them.
scripts/ci/agent_completion_gate.py, the payloadschema, the label taxonomy, and
linear-code[bot]'s labelling behaviour. Thegate stays fail-closed for genuine agent work; nothing about real agent
enforcement is relaxed.
Risk
honest work or lets unverified agent work through.
completion judged not applicable and waved through. It is bounded by keeping
every existing PR-side provenance signal sufficient on its own: agent bot
author, agent branch prefix, agent label on the PR, or an
agent-lock-manifestin the PR body. Only the transitive issue-label path is narrowed, and only
by additionally requiring the contract the label already claims exists.
tests, touches no persisted state, and no other job reads the new notice.
Verification
Run on the current head.
PYTHONPATH=. python3 -m unittest tests.unit.test_agent_completion_gate—106/106 pass, including three new tests.
nodeacross 13 rows,among them the exact fix(aw): require an explicit noop terminal state in agentic workflows #1123 scenario (issue labelled
mcp/agent, body declaresno contract, human author, non-agent branch → not applicable).
test_scheduled_scanner_detects_frozen_intent_changeshad encoded the oldbehaviour. Both were updated to the corrected contract while preserving their
original intent: one split into with-contract and without-contract cases, the
other kept its "applicability follows the selected issue" purpose by giving
the selected issue a declared contract.
test_agent_applicability_copies_stay_identicalpins the two copies of thefunction together so they cannot drift.
Two implementation constraints worth flagging for review, both discovered by
failing tests rather than assumed:
declaresAgentContractis nested insideagentTaskApplicable. The testharness extracts each JS function individually and runs it under
node -e,so an extracted function must be self-contained; a sibling helper breaks the
scheduled-scanner test with
ReferenceError.core.notice(), nevercollectionErrors.push().scripts/ci/agent_completion_gate.pyshort-circuits tonot_applicablewhenapplicable is Falseand discards collection errors, so a pushed error wouldbe silently swallowed — the diagnostic would look implemented and do nothing.
agent-completion/truth-gateis red on this PR, by the bug this PR fixes.The workflow triggers on
pull_request_target, which always evaluates theworkflow file from
main, so the corrected function cannot run on its own PR.This PR closes #1130, #1130 carries the
mcp/agentlabel, and #1130 declares noagent contract — the exact input class described above. The check turns green
for subsequent PRs once this merges.
gitleaks (working tree)is a separaterepo-wide false positive, tracked and fixed in #1159.
Agent completion enforcementfails on every open PR in the repository,human and agent alike, because its trust policy is unprovisioned — surveyed
and tracked in #1160. None of the three is caused by this diff.
Production evidence
Not applicable: no runtime surface changes. This PR modifies CI enforcement
logic and its tests only — no application code, no dependency, no
infrastructure, and nothing that reaches a deployed environment. The observable
evidence for this change is the gate verdict itself, which is why the
verification above exercises the real workflow function rather than a
reimplementation of it.
Agent handoff