Repository navigation
Conversation
…nnot file a finding twice The duplicate check awaits an LLM call and compares the candidate with the findings that were stored when it started. Two agents filing the same finding at the same moment each saw a list without the other's report, passed the check and both stored it. Reproduced against the real create functions with a slow check: three concurrent identical filings were stored three times, while a sequential fourth was correctly rejected. Hold a per-run asyncio lock across read existing -> check -> add in both create_vulnerability_report and create_dependency_report. Distinct findings still go through, one duplicate check at a time. The candidate and field dicts are built before the lock (they do not depend on the stored list), so the diff is mostly indentation: `git diff -w` is 13 lines. Fixes usestrix#627 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TavwVMH7PvrD68JTDfd6Hb
|
| async def _slow_check(candidate: dict[str, Any], existing: list[dict[str, Any]]) -> dict[str, Any]: | ||
| """Stand in for the real check: it judges the candidate against the snapshot it is handed.""" | ||
| await asyncio.sleep(0.05) # the real one is a model call | ||
| for report in existing: | ||
| if report.get("title") == candidate["title"]: |
There was a problem hiding this comment.
Each test starts with no stored findings, but _slow_check always pauses. The real check_duplicate returns right away for an empty list and for matching dependency CVE/package pairs. These tests could pass even if the race returns on a path that waits for the model. Seed an unrelated finding in the vulnerability test and test a dependency case that reaches the model check.
Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/test_reporting_dedupe_race.py
Line: 45-49
Comment:
**Race tests miss real waits**
Each test starts with no stored findings, but `_slow_check` always pauses. The real `check_duplicate` returns right away for an empty list and for matching dependency CVE/package pairs. These tests could pass even if the race returns on a path that waits for the model. Seed an unrelated finding in the vulnerability test and test a dependency case that reaches the model check.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Bump to 1.7.0+shvia.22 and add the FORK.md row plus the four contributions (usestrix#1469 dedupe lock, usestrix#1470 whitebox repository targets, usestrix#1471 test isolation from the real home, usestrix#1472 trust fields in the PDF report). CLAUDE.md now says the suite no longer writes the real ~/.strix, and that `import litellm` loads the current directory's .env unless LITELLM_MODE=PRODUCTION. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TavwVMH7PvrD68JTDfd6Hb
Resolves the conflicts with the retry and reporting changes that landed on main since this branch was opened. The resulting tree is the PR rebased onto main, tested there. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TavwVMH7PvrD68JTDfd6Hb
Fixes #627.
Problem
The duplicate check in
create_vulnerability_reportawaits a model call and compares the candidate with the findings that were stored when it started. Two agents filing the same finding at the same moment each see a list without the other's report, both pass the check, and the run stores the finding twice.Reproduced against the real
_do_createwith the realcheck_duplicate, with one unrelated finding already stored and a stand-in model that pauses like a real call and judges against the reports in the request it is handed:So the check works; it just is not atomic with the add.
Fix
Hold a per-run
asyncio.Lock(ReportState.dedupe_lock) across read stored findings → duplicate check → add increate_vulnerability_report. Distinct findings still go through, one duplicate check at a time. The candidate and field dicts are built before the lock because they do not depend on the stored list, so the diff is mostly indentation:git diff -wis 13 lines.create_dependency_reportis left alone on purpose. For a dependency candidatecheck_duplicatedecides from the CVE / ecosystem / package identity without calling the model, so it never awaits and cannot interleave with another filing. (An earlier revision of this PR also locked that path; it could not be shown to race, so it was dropped.)I looked at #633 first. Its lock covers only the read and its re-check is an exact-title match, and its test re-implements the sequence instead of calling the real function, so I did not build on it.
Trade-off
Filings are serialized. Each waits for the dedupe call (a few seconds) while another is in flight; that is the cost of the check being correct, and runs file a handful of findings.
Tests
tests/test_reporting_dedupe_race.pydrives the real_do_createand the realcheck_duplicate. The real check returns at once while nothing is stored, so each test stores one unrelated finding first and only the model is replaced:Without the lock the same-finding test fails (
[True, True]: both filings succeed); with it all three pass. The settings the test needs are isolated from the developer's stored config.ruff format,ruff checkandmypyare clean on the touched files, and the neighbouring reporting, dedupe and state tests pass (235).🤖 Generated with Claude Code
https://claude.ai/code/session_01TavwVMH7PvrD68JTDfd6Hb