Problem
47 of the 69 open PRs fail agent-completion/truth-gate with a bare invalid_payload and no remediation path. Authors cannot act on it, so PRs accumulate. Example — PR #1270:
{"verdict":"blocked","reasons":["invalid_payload"],
"details":{"invalid_fields":["policy.agent_login","policy.run_id"]}}
Root cause
agentTaskApplicable() (.github/workflows/pr-checks.yml:1904) classifies any branch matching /^(?:agent|claude|codex|copilot|jules)[/-]/i as agent work. Human-authored Claude Code worktree branches (claude/determined-maxwell-*) are therefore held to the full AgentTask provenance contract.
- With no linked AgentTask issue,
expectedAgentLogin / expectedRunId (parsed from the issue body's Agent Login / Agent Run ID sections, pr-checks.yml:2162-2165) resolve empty, so the payload carries policy.agent_login: null and policy.run_id: null.
- The collector does record the real cause in
collection_errors: missing_linked_issue, missing_closing_issue_reference, missing_agent_run_id, missing_agent_login (pr-checks.yml:2166-2176).
evaluate() returns at the schema check (scripts/ci/agent_completion_gate.py:246) and discards them — collection_errors is only read at line 260, after the early return.
Confirmed by reproduction against the exact PR #1270 payload, and by the fact that this issue's own PR uses a fix/ branch prefix and passes the same gate with not_applicable: all rules passed.
Acceptance criteria
Declared files
scripts/ci/agent_completion_gate.py
tests/unit/test_agent_completion_gate.py
Focused test paths
tests/unit/test_agent_completion_gate.py
Out of scope
Changing the agentTaskApplicable() branch-name heuristic itself. That over-triggering is real and worth a separate decision (should PR authorship gate it, rather than branch name alone?), but it changes which PRs are policed, whereas this issue only changes what the gate says.
Problem
47 of the 69 open PRs fail
agent-completion/truth-gatewith a bareinvalid_payloadand no remediation path. Authors cannot act on it, so PRs accumulate. Example — PR #1270:{"verdict":"blocked","reasons":["invalid_payload"], "details":{"invalid_fields":["policy.agent_login","policy.run_id"]}}Root cause
agentTaskApplicable()(.github/workflows/pr-checks.yml:1904) classifies any branch matching/^(?:agent|claude|codex|copilot|jules)[/-]/ias agent work. Human-authored Claude Code worktree branches (claude/determined-maxwell-*) are therefore held to the full AgentTask provenance contract.expectedAgentLogin/expectedRunId(parsed from the issue body's Agent Login / Agent Run ID sections,pr-checks.yml:2162-2165) resolve empty, so the payload carriespolicy.agent_login: nullandpolicy.run_id: null.collection_errors:missing_linked_issue,missing_closing_issue_reference,missing_agent_run_id,missing_agent_login(pr-checks.yml:2166-2176).evaluate()returns at the schema check (scripts/ci/agent_completion_gate.py:246) and discards them —collection_errorsis only read at line 260, after the early return.Confirmed by reproduction against the exact PR #1270 payload, and by the fact that this issue's own PR uses a
fix/branch prefix and passes the same gate withnot_applicable: all rules passed.Acceptance criteria
invalid_payloadverdict includes the collector'scollection_errorsindetails.verdictandreasonsare unchanged for every input — the gate stays fail-closed and noblockedverdict becomesready.collection_errorsare tolerated without raising.Declared files
scripts/ci/agent_completion_gate.pytests/unit/test_agent_completion_gate.pyFocused test paths
tests/unit/test_agent_completion_gate.pyOut of scope
Changing the
agentTaskApplicable()branch-name heuristic itself. That over-triggering is real and worth a separate decision (should PR authorship gate it, rather than branch name alone?), but it changes which PRs are policed, whereas this issue only changes what the gate says.