Skip to content

fix(chat): stop a slow kiro-cli probe from disabling Continue - #4194

Merged
bolichen97 merged 1 commit into
mainfrom
fix/continue-not-readiness-gated
Aug 20, 2026
Merged

fix(chat): stop a slow kiro-cli probe from disabling Continue#4194
bolichen97 merged 1 commit into
mainfrom
fix/continue-not-readiness-gated

Conversation

@CrysisDeu

@CrysisDeu CrysisDeu commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator

What was broken

Pressing Continue on a failed turn did nothing. The button flicked to disabled
and straight back — no message, no turn. Two independent defects met.

1. The endpoint was readiness-gated, and a slow probe reads as signed-out

api_chat_slot_continue opened with reject_if_kiro_unverified. That gate
authorizes on a probe no older than 30s, so it re-probes kiro-cli — and a
probe that merely TIMES OUT is indistinguishable from signed-out. On a host where
the probe runs slow, every press answered 503 kiro_prerequisite_required while
typing the same request by hand worked fine.

Reproduced end to end against an isolated gateway (own KIROCREW_HOME, own port,
live instance untouched) — create slot → send → get the [user, error] transcript
a real failed turn leaves → POST /continue:

POST /api/chat                        HTTP 200   <- sending works
POST /api/chat/slots/<slot>/continue  HTTP 503
    {"error": "Kiro CLI setup or sign-in is required before starting a session.",
     "code": "kiro_prerequisite_required"}

Continue does not belong behind that gate. kiro_readiness's own contract names
three gated classes — poll-driven kiro-cli spawn sites (/api/models,
/api/sessions/usage), destructive reruns (regenerate, edit-resend, rewind), and
POST /v1/chat/completions — and states that ordinary sends are ungated
because a stale latch must not block them. Continue is an ordinary send: it queues
one synthetic continuation and lets the runner dispatch it, mutating nothing
durable up front, so the ACP attempt is its authority and a signed-out install
reports AcpAuthRequired in the transcript. Gating one of two paths that dispatch
the same turn is the defect.

2. The refusal was invisible

handleContinue swallowed every failure into console.warn. The endpoint also
re-checks under slot._lock and can legitimately refuse a press the client
believed was available (slot_running, slot_subagents_running, an approval
pending) — all of which reached the user as a control that promises recovery and
then reports nothing.

What changed

  • api_chat_slot_continue no longer calls reject_if_kiro_unverified; its
    docstring and kiro_readiness.reject_if_kiro_unverified's contract now name
    Continue as a send, so it is not re-added by someone reading either side.
  • Continue joins the existing refused-press surface instead of getting its own:
    one entry in REFUSED_PRESS_TITLE_KEYS, one showRefusedPress('continue', e) in
    the catch. Nothing else on the frontend.
  • docs/system-specs/modules/learn-cron-dashboard.md § readiness updated in the
    same commit.

Rebased onto a main that now owns that surface. #4214 landed the shared
refused-press notice (refusedPress + REFUSED_PRESS_TITLE_KEYS +
showRefusedPress) for regenerate and switch_variant — the generalisation an
earlier revision of this PR had filed as #4202, now closed. The rebase therefore
DELETES this PR's own parallel implementation (its continueError state, its two
effects, its render block) and its refusal test moves into
ChatPage.refusedPress.test.tsx beside the other two presses. Two mechanisms
rendering the same notice would have been this PR's own defect, one level up.

One new string: pages.chatPage.could_not_continue ("Couldn't continue"), the notice's
titled lead, translated in all 11 non-English catalogs with en-XA regenerated. The
refusal text itself is the server's own prose and stays uncatalogued, as backend-owned
strings are today. A first attempt used "Couldn't continue:" and
check-source-strings correctly refused the trailing connector — a key ending
mid-sentence spans two keys and a translator cannot reorder them.

Tests

Both new tests were confirmed to FAIL against the unfixed code, then pass:

  • test_not_readiness_gated wires a not-ready service both ways
    kiro_readiness._service resolves it, so re-adding the gate fails there rather
    than in production. Verified: re-adding the two lines reddens exactly this test.
  • The _patched fixture no longer stubs a readiness gate — that stub is why no
    existing test caught this.
  • ChatPage.refusedPress.test.tsx gains the third press: a rejected continueSlot
    renders Couldn't continue plus the server reason on the shared surface. Its
    existing cases already pin dismissal and turn-start retirement for all three.
  • ChatPage.continueGate.test.tsx keeps the complement — an ACCEPTED continue must
    leave no notice — so the surface cannot drift into firing on the happy path.

Gates run locally after the rebase: pytest (56512 passed), isort, flake8,
mypy (987 files clean), tsc -b, vitest (21440 passed), and
i18n:check (16/16 PASS, 1 new key, 0 badly shaped).

Pre-existing on this host, unrelated to this change: test_file_sheet (16 — this
venv has no openpyxl, which CI installs with the dev group), test_artifact_source
(5), test_artifacts_handlers (2), test_ci_surface_tests (1), and the known
CliPanelCoverage vitest flake (41/41 pass in isolation).

Evidence

Captured through the existing shared harness (website/capture/refused-press.tsx

  • scripts/capture-refused-press.mjs, landed by fix(dashboard): surface refused regenerate/switch-variant via ErrorNotice (#4202) #4238), which now gains a continue
    scene rather than a second harness of its own — see the disposition comment. It mounts
    the real ErrorCard, ErrorNotice and ChatInput out of src/ and reads the titles
    from the live catalog through i18nT, so a frame also proves the new key resolves.
    Every scene asserts its rendered text before a file is written.

The Continue scene renders BOTH controls that raise this refusal — the error card of
the turn that failed and the composer in its recovery state — because that pair is why
a silent refusal here is worse than for the other two presses: with nothing on screen
the reader's theory is that recovery itself is broken.

Couldn't continue — the refusal above the composer

Light theme:

The same refusal in the light theme

The silent state this replaces is the harness's own before scene (composer, no
notice), unchanged by this PR.

Follow-up, deliberately not in this PR

The probe that triggered this is itself broken on the affected host, and its own
SEL audit events say so: kiro_prerequisite_probe_version failed with timeout
200 consecutive times (every probe for ~15 hours), while the whoami identity
probe kept succeeding — the install is signed in; only the --version probe is
slow. _PROBE_TIMEOUT_SECS is 10s and a timeout is read as "unverified", so every
gated endpoint (/api/models, /api/sessions/usage, regenerate, rewind) was
silently refused with nothing on screen for the whole window.

Distinguishing "the probe could not run" from "you are signed out" is a separate
change with its own blast radius, so it is not folded in here. Tracked as #4201.

The five endpoints that keep the gate by design (/api/models,
/api/sessions/usage, regenerate, edit-resend, rewind) therefore still answer 503
forever on such a host — this PR routes around the trigger for continue only,
because continue is the one caller that never belonged behind the gate.

Two sibling presses swallow their refusals into console.warn exactly as continue
did (regenerate failed at ChatPage.tsx:4829, switch-variant failed at :5810),
and regenerate is readiness-gated, so #4201 reaches it as the same invisible 503.
Generalising this PR's notice from one press to any refused press is tracked as
#4202 rather than widened here.

@CrysisDeu
CrysisDeu requested a review from a team August 17, 2026 19:56
@CrysisDeu
CrysisDeu requested a review from a team as a code owner August 17, 2026 19:56
@github-actions github-actions Bot added the readiness: checking Automated validation is still running label Aug 17, 2026
@github-actions

github-actions Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Design Review (Fable 5) — ✅ PASS

Advisory design-level review of 7413979c9af8abdc44070387450cc83344d02f26 — updated in place on each push; does not block merge.

Design-Verdict: PASS

Continue is realigned with the readiness module's own send/gated contract, root cause tracked separately, and the refusal reuses the existing shared surface.

The gate removal is not a workaround dressed as a fix: kiro_readiness's pre-existing contract already ungated ordinary sends because "the real ACP attempt is the authority," and the handler only does an in-memory queue_insert before dispatch — no durable mutation, so it matches the ungated class by the module's own criteria, and test_not_readiness_gated (with the fixture stub removed) pins it against regression. The probe-timeout root cause and the two sibling silent presses are correctly scoped out and tracked. Frontend adds one map entry to a surface main already owns rather than a parallel mechanism. No design-level concerns.

[DESIGN-REVIEWED] 7413979

@github-actions

github-actions Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

First Principles Review (Fable 5) — ✅ PASS

Advisory premise-level review of 7413979c9af8abdc44070387450cc83344d02f26 — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push; does not block merge.

All evidence is read; running the lens checks against the repo confirmed the claims (the shared showRefusedPress surface exists at ChatPage.tsx:4760 with regenerate and switch_variant already on it, the readiness module's contract names ordinary sends as ungated, the five fail-closed callers keep the gate, and temp-screenshots/ + capture harnesses are the repo's standing evidence convention).

First-Principles-Verdict: PASS

Two real defects, each fixed at its cause for this caller, both joining mechanisms that already exist instead of growing new ones.

What this change ships

Intent: make the Continue button actually recover a failed turn, and say why when the server refuses — a FIX.

  1. Continue works on hosts with a slow kiro-cli probe (gate removed) — justified
  2. A refused Continue shows "Couldn't continue" + server reason instead of nothing — justified, joins existing surface
  3. New title string in all 13 locale catalogs — justified, i18n invariant
  4. Readiness contract and spec now name Continue as an ungated send — justified, same-commit spec rule
  5. Backend test pins the gate stays off; fixture stops stubbing the gate that hid the bug — justified
  6. Frontend tests pin refusal-visible and success-silent — justified
  7. Capture harness gains a continue scene (error card + composer) — declared evidence tooling
  8. Two evidence PNGs under temp-screenshots/refused-press/ — declared, repo convention

The gate removal is cause-level for this endpoint: the misclassification (a send treated as a fourth gated class) is deleted, not guarded around. The deeper probe-timeout-reads-as-signed-out defect is named, scoped out with the five still-gated endpoints listed, and tracked (#4201) — exactly what lens 6 asks of an out-of-scope cause. Sibling count for the visibility fix: 0 unfixed — grepped showRefusedPress, all three presses now route through it; the PR deleted its own parallel notice after #4214 landed.

Watch

The description's follow-up section still says generalising the notice "is tracked as #4202 rather than widened here," contradicting its own rebase note that #4202 is closed and #4214 shipped it. Stale paragraph only — the code has one surface.

[FIRST-PRINCIPLES-REVIEWED] 7413979

@github-actions

github-actions Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review — ✅ no blocking findings

GPT 5.6 completed its review of 7413979c9af8abdc44070387450cc83344d02f26 and found no blocking issues.

This comment is updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] 7413979

False positive or not applicable? A repository writer can comment:
/ai-review override gpt 7413979c9af8abdc44070387450cc83344d02f26: <one-sentence reason>

@github-actions

github-actions Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Opus 4.8 Review — ✅ no blocking findings

Reviewed 7413979c9af8abdc44070387450cc83344d02f26 — this comment is updated in place on each push.

Review details

The change is fully coherent: the readiness gate removal on continue is safe (it mutates nothing durable before dispatch, the ACP attempt is the authority, and slot-lock/ownership/sub-agent guards all remain), the frontend catch path correctly routes through the stable showRefusedPress, the RefusedPressAction type is derived from the title-key map that now includes continue, and i18n keys are added to all locales. No candidates were proposed, and I could not ground any new finding to the required bar.

No findings.

[OPUS-REVIEWED] 7413979

Verdict parsed from the review's SHA-scoped output markers for commit 7413979c9af8abdc44070387450cc83344d02f26.

False positive or not applicable? A repository writer can comment:
/ai-review override fable 7413979c9af8abdc44070387450cc83344d02f26: <one-sentence reason>

@github-actions

github-actions Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

UX Review (Fable 5) — ✅ PASS

Advisory UX-level review of 7413979c9af8abdc44070387450cc83344d02f26 — updated in place on each push; does not block merge.

UX-Verdict: PASS

Turns a silent double-failure into a visible, self-retiring notice on the same surface two sibling presses already use — copy, placement, and recovery all hold.

Suggestions

  • The composer control is labeled "Resume" (components.chatInput.resume, visible in continue-dark.png) but its refusal titles itself "Couldn't continue" — since showRefusedPress('continue', e) fires for both the Resume press and the ErrorCard's Continue press, consider "Couldn't resume the turn" or align the two control labels so the refusal names the button the user actually pressed.

[UX-REVIEWED] 7413979

@CrysisDeu
CrysisDeu force-pushed the fix/continue-not-readiness-gated branch 2 times, most recently from 4ce659a to 87d87a5 Compare August 17, 2026 20:35
@CrysisDeu

Copy link
Copy Markdown
Collaborator Author

Zero-context usability review — disposition

A reviewer with no builder context judged the two committed frames as a first-time,
non-technical user. Three findings; one applied, two declined with reasons.

APPLIED — the two red bars read as "two things are wrong", not cause and effect

"I cannot reliably tell which bar is the original problem and which is the
consequence of my click… My most likely conclusion is that my click did nothing
and a second, separate error appeared coincidentally."

Real, and the consequence is a user pressing Continue repeatedly. The notice now
leads with a bold Couldn't continue (new key pages.chatPage.could_not_continue,
translated in all 11 non-English catalogs), which names the action and anchors the
bar to the press. Matches ErrorNotice's own documented convention ("Save failed").

The first attempt used "Couldn't continue:" and check-source-strings correctly
refused it as a trailing connector — a key ending mid-sentence spans two keys and a
translator cannot reorder them. The colon is gone.

The same finding also made the evidence wrong: both frames showed the readiness 503,
which this PR makes unreachable on this path. Re-captured with a slot-lock refusal
(sub-agents are running), which is what a user will actually see.

DECLINED — "Kiro CLI setup or sign-in is required…" is unactionable prose

"tells me nothing I can act on… the only interactive element on that bar is an ✕"

Correct about the string, and this PR is why it stops appearing here: the readiness
gate is removed from continue, so that 503 is no longer one of this surface's
outcomes. The string is backend-owned (kiro_readiness._KIRO_NOT_READY_RESPONSE) and
still reaches /api/models and /api/sessions/usage, so rewriting it belongs with
those callers, not here.

The refusals that DO reach this notice (slot_running, slot_subagents_running,
slot_queue_pending, slot_approval_pending) are terse but describe a state that
passes on its own. Mapping each code to an actionable sentence is a real
improvement and a real scope increase — four new strings × 12 catalogs — and the
codes are already machine-readable, so it can land without touching this diff.

DECLINED — two buttons for one apparent job (Continue in the bar, Resume in the composer)

"Having two hurts. It creates a decision I am not equipped to make."

A fair reading, and out of scope: both buttons predate this PR (#1595 offered the
composer control, #2074 narrowed when it appears) and both call the same handler.
Removing one is a product decision about an existing affordance, not part of making
the existing one stop failing silently. Raised for a maintainer rather than decided
here.

The reviewer also called the word "turn" jargon in The turn ended without a reply
and Last turn was interrupted. Both strings are pre-existing and neither is touched
by this diff.

@CrysisDeu

Copy link
Copy Markdown
Collaborator Author

Review disposition — Design Review (advisory CONCERNS)

FINDING: description↔diff mismatch on locale change

"the PR states 'No new user-facing strings, so no locale change,' but the diff adds
could_not_continue … the claim is wrong"

accepted-and-fixed. The sentence was true at head 4ce659a9c and went stale when
the usability review's titled lead landed at 87d87a527; I edited the code and left
the claim behind. The description now states the one key, its 11 translations plus the
regenerated en-XA, and why the refusal text itself stays uncatalogued. Verifiable
against the diff rather than against my summary.

FINDING: the diagnosed trigger survives on every path still gated

"on the same slow-probe host, regenerate, edit-resend, rewind, and the
poll-driven spawn sites still answer 503 forever … that root fix deserves a tracked
follow-up, or the identical bug report arrives next for Regenerate."

accepted, tracked, not folded in. Correct and precisely the reason this PR is
narrow: continue is removed from the gate because it never belonged there (it is an
ordinary send), which is a different claim from "the gate's premise is wrong". The
premise fix is now #4201, with the measured evidence — 200 consecutive
kiro_prerequisite_probe_version timeouts, whoami succeeding 7987 times over the
same period, so the install is signed in and only the --version probe is slow.

Deliberately not widened here: making verified_ready answer unknown changes the
posture of five gated callers, two of which rewrite persisted history, and that
belongs behind its own review rather than riding a frontend fix. The description now
names the five endpoints that keep the gate, so the scope boundary is in the record
instead of in my head.

@CrysisDeu

Copy link
Copy Markdown
Collaborator Author

Review disposition — First Principles Review (advisory CONCERNS)

SUBTRACTION: delete the "no locale change" paragraph

"the diff itself is the correct record."

accepted. Replaced rather than deleted, because the diff shows the key but not the
reasoning a reviewer needs: which locales, that en-XA is generated, and that the
refusal text stays uncatalogued because backend strings have no catalog path today.
The stale claim is gone.

FINDING: two unfixed sibling presses swallow refusals the same way

"regenerate failed at 4829, switch-variant failed at 5810 … Regenerate stays
readiness-gated, so the identical slow-probe 503 this PR fixes for Continue still
reaches regenerate users as nothing on screen. The deferred probe fix is declared;
this sibling is not."

accepted — the asymmetry was the real defect in the submission, and it is now
declared.
Tracked as #4202, and named in the description alongside #4201.

Not fixed in this diff, and the reason is the invariant itself. Generalising the notice
from one press to any refused press means a per-action title (Couldn't regenerate,
Couldn't switch variant) — 2 new keys across 12 locales — plus deciding whether a
regenerate refusal belongs above the composer at all, given it is triggered from a
message row further up the transcript. That is a design decision about a surface this
PR does not otherwise touch. Doing all three presses in one change is the right shape
(#4202 says so), which is an argument for a separate change, not for widening this one
by two thirds at review time.

What this PR does leave behind for it: the state, the retirement rules (turn start,
session switch), the container recipe, and the harness — so #4202 is a rename and two
call sites, not a redesign.

Framing note

The lane's own summary is the accurate one: "every item earns its place" — the six
items it enumerated are the change, and the two Watch items were both about the
description and the siblings, not about anything shipped.

@github-actions github-actions Bot added readiness: passed Eligible automated validation passed for the current revision and removed readiness: checking Automated validation is still running labels Aug 17, 2026
chenmingwei23 added a commit that referenced this pull request Aug 18, 2026
…tice (#4202)

A regenerate or switch-variant press the server refuses under the slot
lock (a turn already running, a stop in progress, a pending approval, a
readiness probe that timed out) died in console.warn: the control
flicked to disabled and straight back with nothing on screen.

Generalize the refusal surface: one refused-press ErrorNotice above the
composer, fed by any refusable press through showRefusedPress with a
per-action title (pages.chatPage.could_not_regenerate /
could_not_switch_variant across the 12 shipped locales + generated
en-XA), showing the server's own message. The notice retires when a
turn starts, the session changes, or it is dismissed. The continue
press keeps its own path (open PR #4194 owns that surface).

Tests lock in: refused regenerate renders title + server reason;
refused switch-variant renders and dismiss clears; a starting turn
retires the notice. Capture harness + temp-screenshots included.
chenmingwei23 added a commit that referenced this pull request Aug 18, 2026
…tice (#4202)

A regenerate or switch-variant press the server refuses under the slot
lock (a turn already running, a stop in progress, a pending approval, a
readiness probe that timed out) died in console.warn: the control
flicked to disabled and straight back with nothing on screen.

Generalize the refusal surface: one refused-press ErrorNotice above the
composer, fed by any refusable press through showRefusedPress with a
per-action title (pages.chatPage.could_not_regenerate /
could_not_switch_variant across the 12 shipped locales + generated
en-XA), showing the server's own message. The notice retires when a
turn starts, the session changes, or it is dismissed. The continue
press keeps its own path (open PR #4194 owns that surface).

Tests lock in: refused regenerate renders title + server reason;
refused switch-variant renders and dismiss clears; a starting turn
retires the notice. Capture harness + temp-screenshots included.
bolichen97 pushed a commit that referenced this pull request Aug 18, 2026
…tice (#4202) (#4238)

A regenerate or switch-variant press the server refuses under the slot
lock (a turn already running, a stop in progress, a pending approval, a
readiness probe that timed out) died in console.warn: the control
flicked to disabled and straight back with nothing on screen.

Generalize the refusal surface: one refused-press ErrorNotice above the
composer, fed by any refusable press through showRefusedPress with a
per-action title (pages.chatPage.could_not_regenerate /
could_not_switch_variant across the 12 shipped locales + generated
en-XA), showing the server's own message. The notice retires when a
turn starts, the session changes, or it is dismissed. The continue
press keeps its own path (open PR #4194 owns that surface).

Tests lock in: refused regenerate renders title + server reason;
refused switch-variant renders and dismiss clears; a starting turn
retires the notice. Capture harness + temp-screenshots included.
@github-actions github-actions Bot added the merge conflict Branch has merge conflicts with its base — author must resolve before merge label Aug 18, 2026
@CrysisDeu
CrysisDeu force-pushed the fix/continue-not-readiness-gated branch from 87d87a5 to 891ae97 Compare August 18, 2026 19:24
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: passed Eligible automated validation passed for the current revision labels Aug 18, 2026
@CrysisDeu
CrysisDeu force-pushed the fix/continue-not-readiness-gated branch from 891ae97 to 1ff455d Compare August 18, 2026 19:43
@github-actions github-actions Bot removed the merge conflict Branch has merge conflicts with its base — author must resolve before merge label Aug 18, 2026
The Continue button on a failed turn was dead: it flicked to disabled and
straight back, with nothing on screen. Two independent defects met.

The endpoint was readiness-gated. `api_chat_slot_continue` opened with
`reject_if_kiro_unverified`, which authorizes on a probe no older than 30s and
therefore RE-PROBES `kiro-cli`. A probe that merely times out is indistinguishable
from signed-out, so on a host where that probe runs slow every press answered 503
`kiro_prerequisite_required` while typing the same request by hand worked. Continue
is an ordinary send — it queues one synthetic continuation and lets the runner
dispatch it, mutating nothing durable up front — so by `kiro_readiness`'s own
contract the ACP attempt is its authority and a signed-out install reports
`AcpAuthRequired` in the transcript. It is not one of the three gated classes
(poll-driven spawn sites, destructive reruns, `/v1/chat/completions`).

The refusal was also invisible. `handleContinue` swallowed every failure into
`console.warn`, so a server refusal the user could act on — the slot started a
turn, sub-agents are still delivering, an approval is pending — reached them as a
control that promises recovery and then says nothing. The reason now renders above
the composer via the shared `ErrorNotice`, dismissible, and is retired when a turn
starts or the session changes. One surface covers both presses: the error card
hosts one button and the composer the other, and both call `handleContinue`. It
carries a titled lead ("Couldn't continue"), because two equally-red bars stacked
flush read as "two things are wrong" rather than "this is the answer to the button
you just pressed" — a first-time reader then concludes the click did nothing and
presses again.

Reproduced end to end against an isolated gateway before and after. Both new tests
were confirmed to fail against the unfixed code: `test_not_readiness_gated` wires a
not-ready service the way `kiro_readiness._service` resolves it, so re-adding the
gate fails there rather than in production, and the fixture no longer stubs a
readiness gate — that stub is what hid this.
@CrysisDeu
CrysisDeu force-pushed the fix/continue-not-readiness-gated branch from 1ff455d to 7413979 Compare August 18, 2026 21:29
@CrysisDeu

Copy link
Copy Markdown
Collaborator Author

Review disposition — First Principles Review (advisory CONCERNS)

SUBTRACTION: the evidence harness duplicated capture/refused-press.tsx

"the new tsx copies its globalThis.fetch stub verbatim (12 lines, grepped both
files) and the mjs copies the scene-loop structure … The description's own bar —
'Two mechanisms rendering the same notice would have been this PR's own defect, one
level up' — applies to its evidence too."

accepted and applied, exactly as specified. Deleted
website/capture/continue-refusal.{html,tsx}, scripts/capture-continue-refusal.mjs,
the verify:continue-refusal package script, and temp-screenshots/continue-refusal/.
capture/refused-press.tsx gains a continue scene and
capture-refused-press.mjs gains two SCENES rows; the two title maps it already
carried became one TITLE_KEYS record so a fourth press is a data edit there too.

The finding is right about the history as well as the code: that harness landed in
#4238 from this PR's earlier revision, so the duplication was mine returning to me.
Its own argument — one notice surface, one harness — is why the correct answer was to
join it rather than argue scope.

One deliberate difference: the continue scene renders the ErrorCard above the
notice and puts the composer in its recovery state, because Continue is pressed from
TWO controls at once and that pair is why a silent refusal here reads as recovery
itself being broken. withErrorCard gates it, so the other scenes are byte-unchanged
— verified by restoring their committed PNGs after the re-capture and confirming the
staged diff carries only the two new frames.

Watch: the deferred root cause (#4201)

Recorded as accepted-and-deferred by the lane itself. The five endpoints that keep the
gate by design still 503 forever on such a host; that fix changes the posture of
callers which rewrite persisted history, so it stays in #4201 rather than riding here.

@github-actions github-actions Bot added readiness: passed Eligible automated validation passed for the current revision and removed readiness: checking Automated validation is still running labels Aug 18, 2026
@bolichen97
bolichen97 enabled auto-merge (squash) August 20, 2026 03:54
@bolichen97
bolichen97 merged commit bbb87f7 into main Aug 20, 2026
66 of 68 checks passed
@bolichen97
bolichen97 deleted the fix/continue-not-readiness-gated branch August 20, 2026 03:54
@github-actions github-actions Bot removed the readiness: passed Eligible automated validation passed for the current revision label Aug 20, 2026
encomjp pushed a commit to encomjp/kirocrew-customapi that referenced this pull request Aug 22, 2026
…tice (kirodotdev#4202) (kirodotdev#4238)

A regenerate or switch-variant press the server refuses under the slot
lock (a turn already running, a stop in progress, a pending approval, a
readiness probe that timed out) died in console.warn: the control
flicked to disabled and straight back with nothing on screen.

Generalize the refusal surface: one refused-press ErrorNotice above the
composer, fed by any refusable press through showRefusedPress with a
per-action title (pages.chatPage.could_not_regenerate /
could_not_switch_variant across the 12 shipped locales + generated
en-XA), showing the server's own message. The notice retires when a
turn starts, the session changes, or it is dismissed. The continue
press keeps its own path (open PR kirodotdev#4194 owns that surface).

Tests lock in: refused regenerate renders title + server reason;
refused switch-variant renders and dismiss clears; a starting turn
retires the notice. Capture harness + temp-screenshots included.
encomjp pushed a commit to encomjp/kirocrew-customapi that referenced this pull request Aug 22, 2026
…tdev#4194)

The Continue button on a failed turn was dead: it flicked to disabled and
straight back, with nothing on screen. Two independent defects met.

The endpoint was readiness-gated. `api_chat_slot_continue` opened with
`reject_if_kiro_unverified`, which authorizes on a probe no older than 30s and
therefore RE-PROBES `kiro-cli`. A probe that merely times out is indistinguishable
from signed-out, so on a host where that probe runs slow every press answered 503
`kiro_prerequisite_required` while typing the same request by hand worked. Continue
is an ordinary send — it queues one synthetic continuation and lets the runner
dispatch it, mutating nothing durable up front — so by `kiro_readiness`'s own
contract the ACP attempt is its authority and a signed-out install reports
`AcpAuthRequired` in the transcript. It is not one of the three gated classes
(poll-driven spawn sites, destructive reruns, `/v1/chat/completions`).

The refusal was also invisible. `handleContinue` swallowed every failure into
`console.warn`, so a server refusal the user could act on — the slot started a
turn, sub-agents are still delivering, an approval is pending — reached them as a
control that promises recovery and then says nothing. The reason now renders above
the composer via the shared `ErrorNotice`, dismissible, and is retired when a turn
starts or the session changes. One surface covers both presses: the error card
hosts one button and the composer the other, and both call `handleContinue`. It
carries a titled lead ("Couldn't continue"), because two equally-red bars stacked
flush read as "two things are wrong" rather than "this is the answer to the button
you just pressed" — a first-time reader then concludes the click did nothing and
presses again.

Reproduced end to end against an isolated gateway before and after. Both new tests
were confirmed to fail against the unfixed code: `test_not_readiness_gated` wires a
not-ready service the way `kiro_readiness._service` resolves it, so re-adding the
gate fails there rather than in production, and the fixture no longer stubs a
readiness gate — that stub is what hid this.

Co-authored-by: t <t@t>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants