Repository navigation
[Hackathon] fix(plan): same-key accept retries fail after staging is cleared #658
Description
Verified against (commit SHA or released version)
CLI v0.15.0, commit d61e6dfc400f5e88ee34ac799c5496ac5c5a7751, Linux, Node v22.23.3. Reproduced with the real built runPlanAccept and HTTP client, injected HTTP responses, fake credentials, and temporary directories. No TestSprite service calls or credits were used. Backend receipt replay was simulated, not verified against a live account.
Discord Username / User ID
sanaro99
What does this improvement do?
Restore the advertised safe-retry behavior of test plan accept --idempotency-key. After an acceptance clears staging, a second invocation with the same key fails locally before it can ask the server to replay the original receipt.
This matters when a developer or coding agent loses the acceptance response: tests may already have been created, but repeating the same command cannot recover their case keys. The error points toward generating proposals again instead of recovering the committed acceptance.
The confirmed defect is the client-side retry barrier and misleading recovery guidance. This report does not establish duplicate creation, duplicate billing, or loss of created tests.
Details / implementation notes
Reproduction
Given a project with two staged proposals, accept them with a fixed key:
testsprite test plan accept --project p_example --idempotency-key accept-build-42 --output jsonRepeat the exact command after acceptance clears staging. The command performs GET /projects/p_example/plans, sees no proposals, and exits 6 with PRECONDITION_FAILED, reason nothing_staged. It never sends a second POST /projects/p_example/plans/accept or its idempotency key. Repeating with an explicit --only prop_1 selection has the same problem.
The offline reproduction exercised three cases:
| Scenario | First invocation | Same-key retry | Requests across both |
|---|---|---|---|
| Accept all proposals | Returns two case keys | Exit 6, nothing staged | GET, POST, GET |
| Accept one proposal | Returns one case key | Exit 6, nothing staged | GET, POST, GET |
| Accept one, lose response after commit | Response read fails; fixture retains receipt | Exit 6, nothing staged | GET, POST, GET |
All three assertions passed. The fixture stores the accepted request and receipt under the supplied key and can replay it, but the CLI never reaches that endpoint on the second invocation. The first two cases establish the defect without requiring a transport failure; the third illustrates recovery impact.
Source evidence
runPlanAcceptreads staging and rejects an empty batch before acceptance.- The flag help explicitly says to pin a key for safe retries. The same help documents that acceptance clears staging, including subset acceptance.
acceptPlanssends an explicit proposal-ID list and idempotency header.
Expected behavior and fix direction
Confirm the service's receipt-replay contract during triage, then let a retry recover the original acceptance result without requiring the accepted proposals to remain staged. Preserve the original project, key and exact selected IDs, plus any metadata needed to reconstruct the output.
For accept-all, the original ID list cannot be reconstructed from empty staging. A CLI-only approach may need an intent record saved before dispatch; an existing server lookup could be preferable if available. Please agree that mechanism before implementation. An explicit same-key retry must not silently select proposals generated after the original acceptance.
Do not simply remove the guard and send only: []: the documented client contract says that empty list means rejecting everything and clearing staging. Keep genuine first-attempt empty-staging validation and key/body mismatch protection.
Focused regression coverage
- All-proposal and subset acceptance followed by a same-key retry.
- A committed acceptance whose response is lost.
- New proposals staged before the old operation is retried.
- Reusing a key with different selection/project intent.
- A genuinely new request with empty staging.
- Correct recovery output, including the original case keys, without a second creation.
Suggested severity: P2, for broken recovery and automation.
Duplicate check: Public open/closed issue and open-PR searches for plan acceptance, nothing_staged, retries, receipts and idempotency found no matching report or implementation. #492 concerns changing keys on batch run triggers; #431 concerns response-body transport retries; #516 concerns generation resumption. This report concerns a subsequent acceptance invocation being rejected before dispatch, including after a normally successful first invocation.
AI assistance: Codex assisted with public source inspection, the offline reproduction and drafting. No live backend behavior beyond the public client contract is asserted.
Confirmations
- I have searched existing issues and this is not a duplicate.
- I have provided my Discord identity above for reward coordination.
Activity