Repository navigation
v0.23.0 — human prompts go through AskUserQuestion (Stop-hook gate)
[0.23.0] - 2026-10-21
The steward now asks the human directly, and a hook checks that it did.
Added
- Human prompts go through
AskUserQuestion. A new steward section makes every decision or action the human owes anAskUserQuestioncall before the turn ends, in interactive sessions. Prose such as "reply1or2" or a "What I need from you:" list is text the human can scroll past, and a law firm waited a month for an answer that sat in one. The section sets the shape: one question per item, at most 4 per call and more in consecutive calls, context plus the issue link in the question, the recommended option first with its cost, steps or draft text inpreview, and "Done, verify it now" / "Not now" for actions the human must perform. The answers are acted on in the same turn; an answer that names a specific outbound action approves that action only. Anything still owed stays afounder-actionissue (Capture duty 3 is unchanged and remains the only prompt in headless, scheduled and subagent contexts, whereAskUserQuestiondoes not exist). First-touch gains step 6: after the greeting, the openfounder-actionissues owed by the current human (--assignee @me, plus the company's owner-label mapping if its rootCLAUDE.mddeclares one) are asked, deadline first, at most 4, with the remainder counted in one line. Fresh scaffolds and an unauthenticatedghskip it silently..claude/CLAUDE.mdcarries one bullet pointing at the section. shared/scripts/human-prompt-gate.mjs, aStophook. At the end of a turn it analyses only the FINAL assistant message (last_assistant_messagefrom the Stop input, else the last assistant message in the transcript, read from the end of the file, never in full) and blocks the stop once when that message asks the human for something in prose. Earlier messages of the turn are not re-checked, so a request from an earlier stop is not flagged again after a background-task wake-up; a leftover prose request in a final message that follows anAskUserQuestioncall still blocks. English and Russian patterns live in one exportedCTA_PATTERNSarray and match addressed forms only: sentence-initial imperatives (reply, answer, choose, confirm, approve, let me know, type "approve"; ответь, подтверди, дай знать ...), headers ("What I need from you", "Next step for you", "Action required", "Что нужно от тебя"), direct questions (Should I, Want me to, Can you, Which ..., Хочешь ...; any paragraph for the strong forms, the closing paragraph for the rest), "needs your decision / approval / sign-off", a standalone "your call", and a founder-action addressed to the reader. Reporting text such as "should respond with 200", "routes answer 'unauthorised'" or "column of typenumeric" does not match. Only paired fenced code blocks are ignored (a stray ``` hides nothing), so paste-prompts do not trigger it.stop_hook_activeprevents loops, the hook reads `last_assistant_message` because the transcript can lag, and every error exits 0 silently. `OPOS_HUMAN_PROMPT_GATE=off` disables it. Claude Code gives a `Stop` hook no signal for headless runs, so a headless run may spend one extra turn; the block message tells the model to file `founder-action` issues there. Tests: `shared/scripts/human-prompt-gate.test.mjs` (in CI).human-prompt-gatepattern adjustments (still 0.23.0). Russian past tense «Пришли ответы / деньги / письма» no longer blocks; «пришли» is an imperative only with an addressee or object («пришли мне / сюда / вывод / результат»). New forms: sentence-initial "Please " (sign in, log out, check, click, run, paste, reply, confirm, approve; "Please note" and "Please be aware" pass), lead-ins addressed to the human ("What's left for you:", "Two things for you:", "Your step:", «Тебе осталось:», «Твой шаг:»), and short closing questions ("Merge or hold?", "OK to send?", "I need your OK before ...", "Waiting for your go", «Отправляем?», «Подтверждаешь?»; a colon no longer lets a question escape: «Как поступим с инвойсом: платим сейчас или ждём?»). Negations pass: "does not need your approval", "Nothing here needs you", "What I need from you: nothing", "Action required: none", «Нужно от тебя: ничего». Tests added inhuman-prompt-gate.test.mjs. Thehooks.Stopandhooks.UserPromptSubmitrationale inrequired-settings.jsonnow describe the check accurately.human-prompt-gateround 4 (still 0.23.0). Quoted third-party text no longer triggers any rule: «…», “…”, a straight-quoted "…" span (only where the opening quote follows whitespace, an opening bracket, a colon, emphasis marks*/_or a dash, and the closing quote is followed by whitespace, punctuation,*/_or a dash, so an inch mark like5"never opens a span), '…' of 3+ words,>blockquote lines (except a callout, see below), and the rest of the sentence aftererror:/error was:/error message:(up to the sentence end or a spaced dash or hyphen) are stripped first (a 1-3 word quote right after reply / answer / type / say stays, so Reply "go ahead" still blocks, as does an instruction followed by a quoted draft). A negation ("nothing", "nobody", "no action", «ничего», «никто») now neutralises a "needs your ..." request only inside its own clause, ended by;.!?, " but ", " however ", " so ", ", а ", ", но " or a dash. «Пришлите» (plural/formal imperative, never past tense) always blocks; «пришли» is unchanged. Tests added.human-prompt-gateround 5 (still 0.23.0). Stripping quoted text no longer fuses the next sentence (a span that ends a sentence leaves.);said/says/replied/respondedare no longer lead words (reported speech is an action handed to the reader),error/error was/messageremain and stop at a dash; a>line with an explicit action-required callout is kept;5"and'90sare not quote openers; a quoted answer token may be up to 6 words (Type "yes, merge it now"). A negation clause now also ends at,:-and, and; the baresoboundary is, soonly. Tests added.human-prompt-gateround 6 (still 0.23.0). Baremessage:is no longer a lead word (it swallowed real requests such as "I drafted the client message: please review it and approve"); onlyerror:,error was:anderror message:strip the rest of the sentence. A single quote does not open a quoted span before a common elision ('em, 'cause, 'til, 'tis, 'twas, 'n, 'bout, 'round). Known limit, accepted: an unbalanced curly opener still pairs with the next closing quote. Tests added.human-prompt-gateround 7 (still 0.23.0). Straight-quoted error texts wrapped in markdown emphasis or set off by dashes (*"Deployment failed. Please check the build logs."*,**"..."**,printed—"..."—) are stripped again. A>blockquote line is kept when, after removing the>and emphasis marks, it starts with anyen-header/ru-headerphrase ("Action needed", "Next step for you", "Нужно от тебя" ...; the same list as the rules) or holds one of the earlier callout keywords; plain blockquoted quotes still pass. Theerror:strip also stops at a spaced ASCII hyphen, soError: build failed - please paste STRIPE_KEY into the settings.blocks. Tests added.
Changed
- Settings reconciliation appends missing hook entries to an event the consumer already uses.
required-settings.jsongainshooks.Stop. A consumer with noStopkey receives it as before.reconcile-settings.pynow also appends a manifest hook entry to an existinghooks.<Event>array when no entry there runs the same script (matched by script file name, otherwise by exact command), without editing, reordering or removing anything the consumer wrote and without duplicates. This also delivers theSessionStartentries earlier releases could not add to a consumer that already had aSessionStartarray. An explicit alias list (LEGACY_COMMAND_ALIASES) marks the legacy inlinepython3 -cscheduler re-arm command as the same hook asscheduler-rearm-notice.mjs, so a settings file that still carries it is not given a second re-arm reminder; the.claude/settings.jsonshipped to new scaffolds now uses the.mjsentry.permissionsis still never written. Tests:ui/tests/test_reconcile_settings.py.
Known issues
- OPOS hooks are registered with project-relative paths, so a session opened in a subfolder (for example
departments/finance/) getsCannot find modulefrom every OPOS hook, including the newStophook. Pre-existing and not fixed here; tracked in #67.
Migration
- None required. The
Stophook reaches a consumer through the next settings reconciliation (sync-from-core,auto-sync, or the unattended updater). To switch it off for a machine, setOPOS_HUMAN_PROMPT_GATE=off. A consumer whose rootCLAUDE.mdtells agents to end reports with "reply 1 or 2" style instructions should reword them to refer to the steward's new section.