Codex Security 0.1.24 accepts final replacement but retains stale deferred coverage
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 125k
- Forks
- 19.4k
- PR merge metrics
- PR metrics pending
Description
What issue are you seeing?
Codex Security plugin 0.1.24 accepts a final semantic scan draft as a replacement but can silently project stale deferred work from an earlier checkpoint into the canonical coverage and deterministic report.
Both the checkpoint and final call return:
{"operation":"replace","status":"draft_written"}
The final call supplies complete: true and:
{"completeness":"complete","deferred":[],"surfaces":["eight completed surfaces"]}
After completion, coverage.json instead contains completeness: "partial" plus the obsolete six-path deferred row. The generated report likewise says Coverage | partial and includes an Open Questions And Follow Up item for work the final draft declared complete.
This makes the accepted input, acknowledgment, canonical JSON, and deterministic report internally contradictory.
What steps can reproduce the bug?
I reproduced this with a bounded synthetic six-file directory and direct tool calls; no repository security review was run.
- Start a prompt-only Standard scan for the synthetic directory.
- Submit a checkpoint with
complete: falseand:
{
"findings": [],
"coverage": {
"completeness": "partial",
"surfaces": [],
"explicitExclusions": [],
"deferred": [{
"id": "independent-source-review",
"reason": "Independent review and candidate validation remain pending.",
"paths": [
"src/a.js", "src/b.js", "src/c.js",
"test/a.test.js", "test/b.test.js", "test/c.test.js"
]
}]
}
}
- Submit a final replacement with
complete: true,findings: [],coverage.completeness: "complete",coverage.deferred: [], and eightno_issue_foundsurfaces covering the six paths plus the source-review and candidate-validation controls. - Observe that the final call returns
surfaceCount: 8,operation: "replace", andstatus: "draft_written". - Complete the scan and inspect
coverage.jsonandreport.md. The oldindependent-source-reviewrow is restored and coverage is downgraded to partial.
The bundled reconciliation logic explains the behavior: it monotonically preserves prior deferred rows unless candidateId ?? id matches a current finding's provenance or a current rejected/not_applicable surface's candidateId. A generic deferred group such as independent-source-review has no supported terminal closure representation, so it is re-added after the submitted final coverage has already passed semantic validation. The write is still acknowledged as a replacement.
Two controls behave correctly:
- A candidate-addressable deferred row followed by a terminal
rejectedsurface with the samecandidateIdproduces canonical complete coverage, an empty deferred list, and a report with no follow-up. - A genuinely unresolved candidate retained as deferred with a
needs_follow_upsurface produces canonical partial coverage and a matching report follow-up.
What is the expected behavior?
The selected correction is explicit deferred resolution backed by an actionable refusal when resolution is absent or invalid. Deliver it in two stages.
Stage 1: refuse inconsistent completion before any draft write or seal
Reconcile the submitted draft with prior active coverage in memory before validating complete-coverage semantics. If a submission claims complete coverage but the reconciled result still has active deferred work or another condition requiring partial coverage, reject it before replacing canonical artifacts or appending a successful checkpoint.
Return a structured, actionable error identifying the unresolved deferred IDs. For the generic group in this reproduction, explain that the installed version has no supported explicit closure representation and that the caller must retain partial coverage until that capability is available. Do not instruct callers to use a schema field that the installed version does not support.
Existing valid candidate-addressable closure and partial checkpoints must continue to work. The seal operation must also validate the canonical state it will seal, preventing a contradictory complete result from slipping through a different entry point. Sealing an explicitly partial scan remains supported.
Regression: the exact final request above must be refused with independent-source-review identified, without changing canonical draft bytes or creating a success checkpoint. Candidate-resolved completion must still succeed, and unresolved partial coverage must remain partial.
Stage 2: add explicit resolution while retaining the refusal
Add terminal-only coverage.resolvedDeferred records:
{
"coverage": {
"resolvedDeferred": [{
"id": "independent-source-review",
"resolution": "completed",
"surfaceIds": ["reviewed-1", "reviewed-2", "reviewed-3", "reviewed-4", "reviewed-5", "reviewed-6", "reviewed-7", "reviewed-8"],
"receiptRefs": ["artifacts/review/independent-source-review.json"]
}]
}
}
Each closure must name exactly one existing active generic deferred ID, use a supported terminal resolution, reference current completed surfaces, and reference applicable review receipts belonging to the same scan and relevant reviewed scope. Merely providing nonempty arrays is insufficient. Reject unknown or duplicate IDs, conflicting active-and-resolved entries, missing or follow-up surfaces, invalid receipt references, and closure evidence that does not cover the deferred obligation.
Explicit generic resolution must not bypass a separately unresolved candidate. Preserve existing candidate closure through matching finding provenance or rejected/not-applicable candidate surfaces.
Remove only the resolved item from active gaps. Retain its resolution record and evidence links for audit and future reconciliation, so replaying older checkpoints cannot resurrect the item. Keep original checkpoints, candidate validations, review receipts, and previously sealed scans intact.
Reconciliation, validation, and commit must use a consistent scan revision under the existing per-scan concurrency mechanism or equivalent revision check. A concurrent update must be reconciled again or refused; it must not be lost. Acknowledgment, canonical JSON, and generated report must describe the same committed state. Retrying an identical valid closure should be idempotent.
Regression: valid closure produces complete coverage with no active stale row in both canonical JSON and report; missing or invalid closure is refused without altering canonical state; historical-checkpoint replay does not resurrect a resolved row; retries are idempotent; concurrent unresolved work is not lost; candidate validation records and sealed history remain intact.
coverage.deferred: [] alone must never authorize deletion of prior unresolved work.
Additional information
- Plugin version: 0.1.24 (the current version listed in the official changelog on 2026-09-12).
- The same symptom was first observed in a real scan after independent source review and all candidate validations completed; this report uses only synthetic data.
- A fix should clear only the active coverage projection. It must preserve candidate-ledger validations, review receipts, checkpoint history, finding evidence, and sealed artifact digests.
- Recommended regression matrix:
- generic six-path deferred row plus explicit terminal closure -> acknowledgment, canonical JSON, and report all complete with no active deferred row;
- omitted/unsupported closure -> actionable pre-seal refusal;
- unresolved candidate/deferred row -> partial/fail-closed;
- candidate-ledger ordering/validation objects, receipt references, checkpoints, and digests remain intact;
- prior sealed scans are never rewritten.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Trace the bundled reconciliation logic and the seal operation described in the issue, then inspect how coverage.json and report.md are produced. Reproduce the synthetic checkpoint and final replacement sequence, and run the recommended regression matrix. Done means inconsistent completion is refused without changing canonical state, valid deferred resolution remains complete across JSON and the report, and retries or concurrent updates do not lose work.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- cli, security
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100