Skip to content

feat(cli): race-safe file-issue dedup guard (rebase of #871 onto upstream main) - #875

Draft
guyoron1 wants to merge 2 commits into
upstream-mainfrom
feat/file-issue-dedup-guard
Draft

feat(cli): race-safe file-issue dedup guard (rebase of #871 onto upstream main)#875
guyoron1 wants to merge 2 commits into
upstream-mainfrom
feat/file-issue-dedup-guard

Conversation

@guyoron1

Copy link
Copy Markdown
Owner

Draft on the fork — rebase of the agent-authored dedup guard onto current upstream main (2e78d663), for review before any upstream filing.

What this adds

A fullsend file-issue CLI command that wraps issue creation with a deterministic dedup guard: before creating, it searches recent issues (author-scoped, title-similarity, configurable --dedup-window, default 30m) and skips filing if a sufficiently similar issue already exists, returning the existing issue's URL. Intended caller: the retro post-script — but any agent post-script or human can use it.

Files: internal/cli/fileissue.go (+300) with tests (+350), SearchIssues support on the forge interface (internal/forge/forge.go, fake.go, github/github.go, gitlab/issue.go), docs.

Why — the evidence chain (all from real runs)

  1. 2026-07-24 incident: 23 workspace-update PRs closed simultaneously → 23 concurrent retro runs → 5 near-identical issues filed within a 2-minute window. Prompt-level "search for duplicates first" (skills/filing-issues) cannot stop this: all agents searched before any had filed. Search-then-file is a race.
  2. Reproduced twice in the Aug 1–2 twin-path 30-issue benchmark on this fork: the near-duplicate pair went through triage in both paths, and both times triage caught one variant (Retro post-script should deduplicate proposals against recently-filed bot issues before creating new ones #801 / Retro post-script should deduplicate proposals against recently-filed bot issues before creating new ones #849 → closed duplicate) but kept the other (Add post-script dedup guard for concurrent retro proposals #800 / Add post-script dedup guard for concurrent retro proposals #848). Model-judged dedup is inconsistent even without the race.
  3. Recursive proof: because nothing deduplicated the issues, the code agent independently implemented this very fix three times (fork PRs feat(#800): add file-issue CLI command with dedup guard #822, feat(#848): add file-issue CLI command with dedup guard #869, feat(#849): add file-issue CLI command with dedup guard #871 — same 10 files each).

Relation to upstream work

Verification

Cherry-picked from PR #871 (65e1af5c + review-feedback 9f3ad482) onto 2e78d663; two conflicts resolved (import union in forge.go, doc-tree union in cli-internals.md). go build ./... clean; go test ./internal/cli/ ./internal/forge/...3002 passed, 0 failed.

Add a `fullsend file-issue` CLI command that wraps issue creation
with a built-in dedup guard, preventing concurrent retro agent
runs from filing duplicate issues. When 23 workspace-update PRs
were closed simultaneously on 2026-07-24, 5 retro agents
independently filed near-identical issues within a 2-minute window.

The command solves the TOCTOU race by searching for recent issues
with similar titles before creating new ones:

- Add `SearchIssues` to `forge.Client` interface with
  `IssueSearchOptions` (owner, repo, creator, since, state)
- Implement for GitHub (Search API), GitLab (stub), FakeClient
- Title similarity uses Jaccard word-overlap coefficient with
  stop-word filtering (threshold: 0.6)
- JSON output: `{created, url, number, duplicate_of}`
- Search failures are non-fatal (fall through to creation)
- Flags: --creator, --dedup-window, --dry-run

Note: pre-commit could not run in sandbox (network restricted);
the post-script runs it authoritatively on the runner.

Closes #849
- Fix API contract: update IssueSearchOptions.State doc to reflect that
  empty defaults to "open" (not "any state")
- Fix edge case: remove "add" from stop-word list so action verbs
  contribute to similarity scoring; opposite-action titles (e.g.,
  "Add X" vs "Remove X") are no longer falsely matched as duplicates
- Change >= to > for threshold comparison to avoid boundary false
  positives at exactly 0.6
- Add input validation: reject Creator values containing spaces, colons,
  or quotes that could inject search qualifiers
- Add dedup_skipped field to FileIssueResult JSON output so callers can
  detect when the dedup guard was not enforced
- Add explanatory comment for the 0.6 similarity threshold
- Fix self-contradictory comment on fileIssueWithDedup
- Add test for opposite-action edge case and dedup_skipped assertions

Addresses review feedback on #871
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.

1 participant