Summary
#1377 fixed the case where agent-completion/truth-gate armed on a branch-name prefix with no linked issue. The same defect survives one level down: the gate arms on a linked issue that is not an agent task, and is then just as unsatisfiable.
agentTaskApplicable() (.github/workflows/pr-checks.yml, both copies) ends:
return login !== 'dependabot[bot]' &&
(issueDispatch || (pullProvenance && Boolean(selectedIssue)));
issueDispatch is correct — it requires the agent-task label and declaresAgentContract(selectedIssue), i.e. the issue actually declares the run id and login the gate goes on to require.
The second disjunct is not. pullProvenance is true for any branch matching /^(?:agent|claude|codex|copilot|jules)[/-]/, and Boolean(selectedIssue) is true for any linked issue. So a claude/… branch that closes an ordinary issue arms the gate, which then demands policy.agent_login and policy.run_id. Those are only ever populated from the frozen intent snapshot written by snapshot-agent-task-intent, which runs on issues events for dispatched agent tasks. An ordinary issue has no snapshot, so the verdict is permanently invalid_payload no matter what the author does.
The inline comment above the return states the correct rule — "provenance arms the gate only once a linked issue exists to verify against" — but Boolean(selectedIssue) tests only that an issue exists, not that it carries anything to verify against.
Reproduction
Live on #1400 (branch claude/clever-heisenberg-8k227t, closes ordinary issue #1399):
{"details": {"collection_errors": ["incomplete_linked_issue_contract",
"linked_issue_not_agent_task",
"missing_intent_snapshot",
"missing_agent_run_id",
"missing_agent_login"],
"invalid_fields": ["policy.agent_login", "policy.run_id"]},
"reasons": ["invalid_payload"], "verdict": "blocked"}
The collector already diagnoses this precisely — linked_issue_not_agent_task and missing_intent_snapshot — the applicability predicate just does not consult it.
Replaying both predicates verbatim against #1400's PR and issue:
agentBranch = true
pullProvenance = true
declaresAgentContract = false
issueDispatch = false
current (pullProvenance && Boolean(selectedIssue)) -> applicable = true # armed, unsatisfiable
proposed (pullProvenance && declaresAgentContract(selected)) -> applicable = false # not_applicable
Proposed fix
return login !== 'dependabot[bot]' &&
- (issueDispatch || (pullProvenance && Boolean(selectedIssue)));
+ (issueDispatch || (pullProvenance && declaresAgentContract(selectedIssue)));
The predicate then reduces to declaresAgentContract(selectedIssue) && (agentTaskLabel || pullProvenance) — the gate arms exactly when a contract exists to measure against, which is what the comment already claims.
This narrows a permanently-failing check, not a working one. A PR linking a genuinely dispatched issue is gated exactly as before, because declaresAgentContract is the same function issueDispatch already relies on. Binding a PR to a focused issue remains separately enforced by Canonical issue and evidence and PR Governance.
Acceptance criteria
Test paths
tests/unit/test_agent_completion_gate.py
Note on rollout
pull_request_target runs the workflow from the base branch, so — as with #1377 — the fix cannot green its own PR. It takes effect for everything else once merged.
Related
Summary
#1377 fixed the case where
agent-completion/truth-gatearmed on a branch-name prefix with no linked issue. The same defect survives one level down: the gate arms on a linked issue that is not an agent task, and is then just as unsatisfiable.agentTaskApplicable()(.github/workflows/pr-checks.yml, both copies) ends:issueDispatchis correct — it requires the agent-task label anddeclaresAgentContract(selectedIssue), i.e. the issue actually declares the run id and login the gate goes on to require.The second disjunct is not.
pullProvenanceis true for any branch matching/^(?:agent|claude|codex|copilot|jules)[/-]/, andBoolean(selectedIssue)is true for any linked issue. So aclaude/…branch that closes an ordinary issue arms the gate, which then demandspolicy.agent_loginandpolicy.run_id. Those are only ever populated from the frozen intent snapshot written bysnapshot-agent-task-intent, which runs onissuesevents for dispatched agent tasks. An ordinary issue has no snapshot, so the verdict is permanentlyinvalid_payloadno matter what the author does.The inline comment above the return states the correct rule — "provenance arms the gate only once a linked issue exists to verify against" — but
Boolean(selectedIssue)tests only that an issue exists, not that it carries anything to verify against.Reproduction
Live on #1400 (branch
claude/clever-heisenberg-8k227t, closes ordinary issue #1399):{"details": {"collection_errors": ["incomplete_linked_issue_contract", "linked_issue_not_agent_task", "missing_intent_snapshot", "missing_agent_run_id", "missing_agent_login"], "invalid_fields": ["policy.agent_login", "policy.run_id"]}, "reasons": ["invalid_payload"], "verdict": "blocked"}The collector already diagnoses this precisely —
linked_issue_not_agent_taskandmissing_intent_snapshot— the applicability predicate just does not consult it.Replaying both predicates verbatim against #1400's PR and issue:
Proposed fix
The predicate then reduces to
declaresAgentContract(selectedIssue) && (agentTaskLabel || pullProvenance)— the gate arms exactly when a contract exists to measure against, which is what the comment already claims.This narrows a permanently-failing check, not a working one. A PR linking a genuinely dispatched issue is gated exactly as before, because
declaresAgentContractis the same functionissueDispatchalready relies on. Binding a PR to a focused issue remains separately enforced byCanonical issue and evidenceandPR Governance.Acceptance criteria
agentTaskApplicable()usedeclaresAgentContract(selectedIssue)in place ofBoolean(selectedIssue), and remain byte-identical to each other (the existing test asserts this).not_applicable.tests/unit/test_agent_completion_gate.pycovers both directions in both assertion blocks.Test paths
tests/unit/test_agent_completion_gate.pyNote on rollout
pull_request_targetruns the workflow from the base branch, so — as with #1377 — the fix cannot green its own PR. It takes effect for everything else once merged.Related
[aw]issue #1130 — the earlier bare-label variant.