fix(server): /answer takes a question id and refuses a stale reply - #95
Merged
Conversation
Closes the terminal-vs-dashboard race from #94's review: while the CLI blocks on input, the same gate can be answered in the dashboard and the run can open a new question — the terminal reply then landed on the wrong question with a 202. /answer now accepts the question id the caller is reading from the trace and returns 409 when it is no longer the open one; the CLI re-prompts for the current question. A missing id keeps the old behaviour.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Closes the correctness gap queued from #94's review (Fable finding 1). While
claw runblocks on the terminal prompt, the same gate can be answered in the dashboard and the run can open a new question — the terminal reply, composed for the first question, then landed on the second with a 202. The window is as wide as the human's typing time, so it is a real race, not a microsecond one.Fix — in the API, not in a rule
Per the project's "constraints belong in the API" principle, the reply now carries the id of the question it answers, and the server enforces the match:
POST /api/projects/{key}/issues/{id}/answeraccepts{"text": …, "question": <id>}. Whenquestionis present and is not the gate the run is currently open on (Server::openQuestionId()→ the newest unansweredquestionfor the issue's running run, via the existingTraceReader::openGate()), the server returns 409that question has been answered — a newer one is waiting.spanId) and sends it. On the 409 it surfaces the server's own reason and, because the trace already carries the newerquestionrow, re-prompts for the current question on the next poll — self-correcting.questionid keeps the previous behaviour (backward compatible with the dashboard and older callers).Why this is the right shape
The CLI can't close the race alone —
fgets()blocks and polls nothing while the person types, and the old body carried no id to check. The server is the only party that knows which question is open, so the check belongs there; the id makes a stale reply unexpressible rather than merely discouraged.Not live-verified
Same caveat as #94: a real gate fires non-deterministically, so the interactive path isn't exercised end-to-end. The server-side check and the client's id-carrying/409-surfacing are unit-tested.
Checks
composer qagreen — cs, PHPStan level 8, Testo (461 tests). New tests: the answer body names the question (and omits it when 0), and a 409 surfaces the server's own message.