Repository navigation
Withdraw the parent's linked question once courier_answer answers it - #55
Conversation
A parent that asks the person a child's question and calls courier_answer in the same step left its own question open, and the person's pick there was dropped without a word. Now any answer claimed for a question withdraws the top session's call linked to it, whether the child's call waits or was cut off and gets the answer as a message. The withdrawn result names the answers that went out and returns them as output.answers, with metadata.withdrawn. For the one race withdrawing cannot close, a pick that lands just as the answer goes through, the parent's tool result now says the question had already been answered and the pick was not passed on, and a dismissal that lands then is logged instead of dropped silently. The mock model of the live test can stream two tool calls in one step, and COURIER-QUESTION-BOTH makes the parent slip that way. Closes #54 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QbKM8QcDsNCdyc8m3XvxtK
The child's form goes within a moment of the notice, so polling for it raced. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QbKM8QcDsNCdyc8m3XvxtK
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QbKM8QcDsNCdyc8m3XvxtK
…ment the held dismissal From the review: deliver also returns nothing when the question was settled some other way, and the second question a held dismissal withdraws was not in docs/reference.md. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QbKM8QcDsNCdyc8m3XvxtK
Copied from riviera-sunbed-booking's .claude/settings.json: the review gate runs the plugin on the PR number, so a session in this repository has it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QbKM8QcDsNCdyc8m3XvxtK
COURIER-ASK-AGAIN makes the mock parent ask the last question it was told of once more; prompted over the API within the two seconds a dismissal is held, its new question is linked, then withdrawn when the dismissal goes through. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QbKM8QcDsNCdyc8m3XvxtK
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. Procedure of the opencode-courier/src/question.ts Lines 851 to 855 in e2183e3 opencode-courier/docs/reference.md Lines 125 to 129 in e2183e3 Lines 293 to 303 in e2183e3 opencode-courier/docs/real-model.md Lines 330 to 338 in e2183e3 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. Generated by Claude Code |
|



Closes #54
A parent that asks the person a child's question with its question tool and calls
courier_answerin the same step (two parallel tool calls, a model slip) left its own question open, and the person's pick there was dropped with a note that said only "nothing was passed on". This implements the decision on the issue: option 1 plus option 2's message.What changed
passOn, right after the answer is claimed, so it covers both paths: the child's waiting call (ended with{ by: "top" }) and the cut-off call, whose answer goes out as a message. In the message path the link goes only once the message is out, so a failed send still leaves the parent's question open and the question answerable again.Session … no longer waits on this question: it was already answered with courier_answer (User has answered your questions: …). There is nothing to pass on; tell the person it is settled.It returns those answers asoutput.answerswithmetadata.withdrawn: true, like the other withdrawn results.courier_answergoes through):passedNote(undefined)now says… it had already been answered or settled, so these answers were not passed on; tell the person so.instead ofnothing was passed on.linkedCallFailed→deliverreturningundefinednow logscourier question: the dismissal of … was not passed on: it had already been answered or settled.courier_answerwhile a linked call is open..claude/settings.jsonenables thecode-reviewplugin at project scope, copied from riviera-sunbed-booking, so the review gate has it in later sessions. Unrelated to A courier_answer leaves the parent's linked question open, and the person's pick is dropped #54.Decisions on the "your call" points
linkgets a wider type of its own,Withdrawal = Elsewhere | { by: "top"; outcome: Outcome }, rather than a newElsewherevariant:settledHow/settledNoticenever see the new case, as the research suggested.Outcome, not justanswers. A dismissal can reachpassOnwith a link set too: the person dismisses the parent's linked question, the dismissal is held for the two-second grace, the parent asks again within it (linked anew), and the held dismissal then ends the child's call. That second question is withdrawn as well (… it was already dismissed in your session, so it carries on without the answers), instead of staying open for a pick that would be dropped. A unit test and a live-test scenario cover it, anddocs/reference.mdhas a clause on it.withdrawnResultis split intowithdrawnHow(the wording and the answers that count, by variant) and the result itself, to keep each small.Tests
test/question.test.ts: the test from Work down the SonarCloud findings in src/question.ts #51 that pinned the old behaviour is rewritten to expect the withdrawn result naming the answer, both forms cancelled, nothing stored, and a secondcourier_answerreturninganswered: false. New tests: a cut-off child with a linked parent pluscourier_answer(the parent's call withdrawn, the child gets exactly one message); a pick that lands whilecourier_answer's answer is still going out (the new note, the pick not passed on); a dismissal that lands aftercourier_answer(dropped and logged); the dismissal-during-grace relink above.e2e/mock-model.mjscan now stream several tool calls in one step (calls), andCOURIER-QUESTION-BOTHmakes the parent emitquestionandcourier_answertogether. The new scenario ine2e/run.shchecks that the parent was told, its question result carries the withdrawn text with the answers,courier_answerpassed them on, the parent's form is gone unanswered, and the child carried on with thecourier_answerpick. A second scenario dismisses the parent's question and prompts it over the API to ask again (COURIER-ASK-AGAIN) inside the two seconds the dismissal is held: the new question is withdrawn, and the child reports that it was dismissed.docs/reference.mdgets the bullet in the "whichever answer comes first" list and the clause on the held dismissal; README's relay summary says whichever answer reaches the child first counts, bycourier_answertoo.COURIER_SCENARIO=questionon OpenCode Zen (longcat-2.5-preview-free,opencode v2.0.23): all six checks passed, linked; recorded indocs/real-model.md. The reworded results are rare-path ones the scenario does not reach.Verification on the head:
bun run typecheck,bun test(240 pass),bun run build,e2e/run.shon@opencode/cli@2.0.23: all 149 checks pass.Review round
The
code-reviewplugin's procedure was run on the PR (five independent reviewers, then confidence scoring; the plugin itself could not be loaded into the running session, so its command was followed by hand). No defect in the change; four low-confidence findings (50 to 75, under the plugin's reporting bar), all addressed: the "not passed on" note and the dismissal log say "answered or settled", sincedeliveralso returns nothing for a question settled some other way; the held-dismissal withdrawal is documented and has a live-test scenario; the real-model run is recorded; the settings file is intentional (above).🤖 Generated with Claude Code
https://claude.ai/code/session_01QbKM8QcDsNCdyc8m3XvxtK