Skip to content

fix: deterministically assign glob-matched files uncovered by selection#527

Merged
piekstra merged 2 commits into
mainfrom
fix-selection-glob-coverage
Jul 23, 2026
Merged

fix: deterministically assign glob-matched files uncovered by selection#527
piekstra merged 2 commits into
mainfrom
fix-selection-glob-coverage

Conversation

@piekstra

Copy link
Copy Markdown
Contributor

Summary

Fixes #526 — the selection stage, when resumed on the long-lived per-PR session, anchors on file assignments from earlier passes and ignores updated .codereview/agents/ definitions. Files its agents' globs now match land in the unassigned coverage bucket, and hasIncompleteReviewerCoverage then downgrades every approve verdict to comment, permanently (each re-review resumes the same session — full evidence with run-artifact session/token forensics in the issue).

Approach

Rather than changing session semantics, add a deterministic backstop in runSelectionPhase after ensureRequiredOnMatchAgents and capSelectionAgents:

  • ensureSelectedGlobCoverage assigns every changed file outside all selected agents' scopes to each selected agent whose file_globs match it.
  • Files matching no selected agent remain unassigned — the coverage gate still catches genuinely uncovered files.
  • Scope semantics preserved: AllowedFiles is only extended when it is what defines the agent's scope (reviewerAssignmentScope prefers it), and agents with no explicit files (full scope) are untouched.
  • globsMatchFile is extracted from requiredOnMatchFiles (same gobwas/glob compile + **/ root-anchor handling) for reuse.

This guarantees the invariant the coverage gate implicitly assumes: a changed file that matches a selected agent's globs is never unassigned, regardless of model anchoring or sloppiness.

Testing

  • TestEnsureSelectedGlobCoverageAssignsUncoveredMatchingFiles — the Selection stage resumes per-PR session and anchors on stale file assignments when agent definitions change #526 shape: uncovered CHANGELOG/testdata files get assigned to matching selected agents (Files-scoped and AllowedFiles-scoped variants), non-matching files stay unassigned, unrelated agents untouched.
  • TestEnsureSelectedGlobCoverageLeavesFullScopeAgentsAlone.
  • go build ./..., go vet ./... clean; full go test ./... passes except TestDryRunResumesPinnedReviewRunByPinnedSHAs / TestDryRunWithPinnedReviewSHAsUsesCompareDiffAndPinnedFileRefs, which fail identically on unmodified main in this environment (git temp-file creation in the test fixture) — pre-existing, unrelated.

Follow-up candidates (not in this PR)

  • Run selection on a fresh session when the agent-definition fingerprint changed (root-cause fix for the anchoring itself).
  • review.noop_same_head means config-only changes can never trigger a re-review without an empty commit — worth a --rerun-style escape via re-request.

https://claude.ai/code/session_01NfzotSGPeRHabbn66nNkEC

The orchestrator-selection model proposes file-to-reviewer assignments,
but a resumed per-PR session can anchor on assignments from an earlier
pass and ignore updated agent definitions (#526). Changed files its
agents' globs now match then land in the unassigned coverage bucket,
and hasIncompleteReviewerCoverage downgrades every approve verdict to
comment - permanently, since each re-review resumes the same session.

Add ensureSelectedGlobCoverage after required-on-match injection and
agent capping: any changed file outside every selected agent's scope is
assigned to each selected agent whose file_globs match it. Files that
match no selected agent still surface as unassigned, so the gate keeps
catching genuinely uncovered files. Scope semantics are preserved -
AllowedFiles is only extended when it defines the agent's scope, and
full-scope agents (no explicit files) are untouched.

Extracts globsMatchFile from requiredOnMatchFiles for reuse.

Fixes #526

Claude-Session: https://claude.ai/code/session_01NfzotSGPeRHabbn66nNkEC
@piekstra
piekstra requested a review from piekstra-dev July 23, 2026 01:28

@piekstra-dev piekstra-dev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Automated PR Review

Reviewed commit: e6d48e86d179
Profile: reviewer - Posting as: piekstra-dev

Summary

Reviewer Findings
go:implementation-tests 0
structure:repo-health 1
structure:repo-health (1 finding)

Major - internal/pipeline/pipeline.go:1343

ensureSelectedGlobCoverage can assign one uncovered file to every selected agent whose glob matches it (confirmed by TestEnsureSelectedGlobCoverageAssignsUncoveredMatchingFiles, where CHANGELOG.md lands in both shared:go and shared:docs). buildReviewerCoverage then folds each selected agent's widened Files/AllowedFiles into that agent's own scope (reviewerAssignmentScope, pipeline.go:1979), and requires that agent to explicitly inspect-or-skip every file in its own scope or its status becomes incomplete_skipped (reviewplan.go coverageMissingFiles). hasIncompleteReviewerCoverage treats incomplete_skipped exactly like incomplete_unassigned and downgrades approve to comment (reviewplan.go:339-352, 450). So this backstop trades one failure mode (#526's single unassigned bucket entry) for a multiplied one: any changed file whose glob happens to match several selected agents (common when generic reviewers share broad globs, e.g. **/*.go plus a docs-file exception like CHANGELOG.md) now obligates every one of those agents to individually account for it, even though the orchestrator only meant one of them to own it. A single non-code file matching several agents' globs can silently downgrade approvals from agents that never intended to review it and have no reason to mention it in inspected/skipped lists. Consider capping assignment to the single best-matching selected agent (e.g. first/most-specific glob owner) rather than fanning out to all matches, or excluding non-primary-scope agents from the coverage requirement for backstop-only assignments.

Reviewer Coverage

Reviewer Status Inspected Skipped Constraints
go:implementation-tests complete_broad internal/pipeline/pipeline.go, internal/pipeline/pipeline_test.go, internal/pipeline/prompts.go unavailable Sandbox did not permit running go build/go vet/go test in the checked-out repo; correctness was verified by static reading of pipeline.go, prompts.go, and pipeline_test.go rather than execution.
structure:repo-health complete_broad internal/pipeline/pipeline.go, internal/pipeline/pipeline_test.go, internal/pipeline/prompts.go unavailable unavailable

0 PR discussion threads considered. 0 summarized; 0 resolved.


Completed in 3m 17s | $2.40 | claude-sonnet-5 | cr 0.10.265
Field Value
Model claude-sonnet-5
Reviewers go:implementation-tests, structure:repo-health
Engine claude_cli · claude-sonnet-5
Reviewed by cr · piekstra-dev
Duration 3m 17s wall · 5m 33s compute
Cost $2.40
Tokens 104 in / 24.2k out

Per-workstream usage

Workstream Model In Out Cache read Cache create Cost Duration
orchestrator-selection claude-sonnet-5 6 1.6k 21.8k 33.8k $0.23 23s
go:implementation-tests claude-sonnet-5 42 10.7k 914.1k 86.7k $0.96 2m 35s
structure:repo-health claude-sonnet-5 50 11.4k 1.2M 91.6k $1.09 2m 25s
orchestrator-rollup claude-sonnet-5 6 419 59.7k 16.4k $0.12 9s

Comment thread internal/pipeline/pipeline.go
Address PR #527 review: assigning an uncovered file to every matching
selected agent widens each agent's scope, and coverage requires every
agent to inspect-or-skip everything in its own scope — so a single file
matching several agents' globs multiplied the obligation and let any
one economizing reviewer downgrade coverage to incomplete_skipped.
Assign each uncovered file to the first selected agent whose globs
match instead.

Claude-Session: https://claude.ai/code/session_01NfzotSGPeRHabbn66nNkEC
@piekstra
piekstra requested a review from piekstra-dev July 23, 2026 01:35

@piekstra-dev piekstra-dev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Automated PR Review

Reviewed commit: 349098120f42
Profile: reviewer - Posting as: piekstra-dev

Summary

Reviewer Findings
go:implementation-tests 0
structure:repo-health 1
structure:repo-health (1 finding)

Minor - internal/pipeline/pipeline.go:1333

ensureSelectedGlobCoverage silently reassigns coverage ownership without leaving any trace of why a file ended up in an agent's Files/AllowedFiles. Compare capSelectionAgents (pipeline.go:1254), which calls opts.emitWarning when it truncates a selection, and ensureRequiredOnMatchAgents (pipeline.go:1292), which at least stamps a distinguishing Rationale ("required_on_match matched changed files") on agents it adds. Here, a file force-assigned to an already-selected agent is appended straight into that agent's existing Files/AllowedFiles with the agent's original orchestrator Rationale left untouched, so selection.json, the reviewer prompt, and the progress events (emitReviewerSelection) can't distinguish an orchestrator-chosen file from one this backstop injected. The whole point of this PR is to compensate for exactly the kind of anchoring bug (#526) whose diagnosis required deep run-artifact/session forensics per the PR description; a future recurrence of stale-session drift that this backstop silently papers over will be just as hard to detect from the artifacts, since coverage will look complete with no signal that the backstop fired. Emit a warning (opts.emitWarning, mirroring capSelectionAgents) or record a lightweight marker (e.g. a distinct rationale suffix or a coverage diagnostic) when ensureSelectedGlobCoverage actually assigns a file, so operators can tell 'model selected this' from 'backstop assigned this' when debugging future coverage anomalies.

Reviewer Coverage

Reviewer Status Inspected Skipped Constraints
go:implementation-tests complete_broad internal/pipeline/pipeline.go, internal/pipeline/pipeline_test.go, internal/pipeline/prompts.go unavailable unavailable
structure:repo-health complete_broad internal/pipeline/pipeline.go, internal/pipeline/pipeline_test.go, internal/pipeline/prompts.go unavailable unavailable

1 PR discussion threads considered. 1 summarized; 1 resolved.


Completed in 3m 19s | $2.01 | claude-sonnet-5 | cr 0.10.265
Field Value
Model claude-sonnet-5
Reviewers go:implementation-tests, structure:repo-health
Engine claude_cli · claude-sonnet-5
Reviewed by cr · piekstra-dev
Duration 3m 19s wall · 4m 28s compute
Cost $2.01
Tokens 74 in / 21.8k out

Per-workstream usage

Workstream Model In Out Cache read Cache create Cost Duration
orchestrator-selection claude-sonnet-5 6 979 69.7k 23.7k $0.18 13s
go:implementation-tests claude-sonnet-5 28 8.5k 579.6k 72.3k $0.74 1m 41s
structure:repo-health claude-sonnet-5 34 12.0k 802.3k 81.0k $0.91 2m 23s
orchestrator-rollup claude-sonnet-5 6 405 80.7k 26.8k $0.19 10s

// Agents whose scope is already all changed files (no explicit
// Files/AllowedFiles) need no widening, and AllowedFiles is only extended
// when it is what defines the agent's scope.
func ensureSelectedGlobCoverage(selection llm.Selection, catalog agents.Catalog, changedFiles []string) llm.Selection {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

ensureSelectedGlobCoverage silently reassigns coverage ownership without leaving any trace of why a file ended up in an agent's Files/AllowedFiles. Compare capSelectionAgents (pipeline.go:1254), which calls opts.emitWarning when it truncates a selection, and ensureRequiredOnMatchAgents (pipeline.go:1292), which at least stamps a distinguishing Rationale ("required_on_match matched changed files") on agents it adds. Here, a file force-assigned to an already-selected agent is appended straight into that agent's existing Files/AllowedFiles with the agent's original orchestrator Rationale left untouched, so selection.json, the reviewer prompt, and the progress events (emitReviewerSelection) can't distinguish an orchestrator-chosen file from one this backstop injected. The whole point of this PR is to compensate for exactly the kind of anchoring bug (#526) whose diagnosis required deep run-artifact/session forensics per the PR description; a future recurrence of stale-session drift that this backstop silently papers over will be just as hard to detect from the artifacts, since coverage will look complete with no signal that the backstop fired. Emit a warning (opts.emitWarning, mirroring capSelectionAgents) or record a lightweight marker (e.g. a distinct rationale suffix or a coverage diagnostic) when ensureSelectedGlobCoverage actually assigns a file, so operators can tell 'model selected this' from 'backstop assigned this' when debugging future coverage anomalies.

Reply inline to this comment.

@piekstra
piekstra merged commit dab3e7e into main Jul 23, 2026
10 checks passed
@piekstra
piekstra deleted the fix-selection-glob-coverage branch July 23, 2026 01:40
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.

Selection stage resumes per-PR session and anchors on stale file assignments when agent definitions change

2 participants