Skip to content

feat(chat): take back a steer the running turn has not read yet - #23388

Open
unstablebrainiac wants to merge 1 commit into
mainfrom
unstablebrainiac/feat/pending-steer-actions
Open

unstablebrainiac wants to merge 1 commit into
mainfrom
unstablebrainiac/feat/pending-steer-actions

Conversation

@unstablebrainiac

@unstablebrainiac unstablebrainiac commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Problem / Motivation

Goal: A person can take back a steer the running turn has not read yet: Unsend it, Edit it, or send it as a queued message instead.

A message sent while the agent works becomes a steer, and it waits until the turn reaches a point where it can read it. Until now it could not be changed in that window. The only ways out were a second steer that contradicts the first, or stopping the whole turn.

Why it matters

Steers are sent fast: a typo, a half-thought, or Enter pressed in Steer mode when Queue was meant. A wrong steer redirects work that was on track. Taking it back before the agent reads it costs the turn nothing.

Not a goal

  • Taking back a steer the turn has already read. Nothing can, and the controls go away once the turn reads it.
  • Harnesses other than kiro-cli. KAS answers the same request, but it names steers its turn already read, so it is left out (measured). codex, claude, goose and opencode have no such request.
  • Messaging channels, Side Chat and crewmate threads. Their steers are not marked as retractable.
  • Editing a steer in place inside its bubble.

What changed (motivation → approach → change)

kiro-cli 2.33 can drop the steers it still holds. _session/steer/clear answers {cleared: true}, then sends AgentExecutionUserMessageCleared naming exactly the steers it dropped. Two live captures in test/fixtures/acp_frames/kiro/ pin the contract: a dropped steer never reaches the model, a steer the turn already read is never named, and a clear with nothing pending reports nothing.

A pending steer that can be taken back shows Queue instead beside "Steering…", with Edit and Unsend one menu deep. The two controls sit apart from the status and its explainer, and Queue instead is the one-click choice because it is the action #18190 asks for: the message still runs, after this turn. The click arms the take-back and writes the clear. The outcome comes from what the turn reports next, never from the clear's answer, which is the same either way:

The turn reports The row shows The text
the drop names it the bubble shrinks into a note in its place, such as "You moved this message to the queue" Unsend: gone. Edit: back in the message box, with the cursor on it. Queue instead: in the queue above the message box
it read the steer first the usual "Steered into the running turn" already ran
the turn ended first Unsend/Edit: the note. Queue instead: "runs as its own message" Queue instead runs next, as asked
nothing before a hard Stop unchanged, since the Stop cannot tell whether the turn read it Edit and Queue instead: back in the message box. Unsend: kept

The clear drops every steer kiro-cli holds, so the other pending steers are sent again in their order. A steer sent while a clear is in flight waits for the clear's answer, so the clear cannot drop it, and it can reach the turn before the steers being sent again. Only one take-back runs at a time per chat, even across browser tabs. Crew escapes <user_message> inside a steer's text, so kiro-cli's echo of what the turn read splits one way; a steer of unknown delivery is never taken back or sent again.

The capability is opt-in per harness (ACP_BACKENDS_STEER_RETRACT, kiro-cli only, read through supports_steer_retract). The gateway marks a steer row as retractable only when the composer's own human sent it on such a harness, so the dashboard draws the controls from the row alone. Each new turn first withdraws the mark from rows a hard stop or a restart left. A taken-back row keeps its place with steerState: retracted but loses its text, so nothing that later reads the transcript as conversation (a cold start's replay, memory, a summary, a fork) presents it as something the person said; a cold start's replay, an interrupted or stopped turn's restore and Regenerate step past the row to the request before it, and the pinned prompt banner never shows it.

The note cannot quote the message, whose text is removed on purpose; the bubble collapses into it where it stood, and the text carries on where it went. The Edit and Queue instead notes are buttons to the message box, with the queue stack just above it. Edit also puts the cursor on the restored text. A row read back already taken back, or with reduced motion, shows the note without the collapse. The page keeps each chat's taken-back text until the row settles, through an unanswered request, a chat switch or a reload, and puts it back only into that chat's message box, even when the row is outside the loaded history. A request that fails says so under the progress line ("Couldn't confirm it was taken back. This updates when the agent reaches it.") while the row waits for what the turn reports.

Where What
src/kiro_crew/acp/, src/kiro_crew/agent_sdk/, src/kiro_crew/acp_backends.py, src/kiro_crew/providers/ clear_pending_steers on the session handle, the dispatch yielding EVENT_STEER_DROPPED, wrap_user_message for every steer write, the ACP_BACKENDS_STEER_RETRACT table (re-exported, classified off-card), and safe LLMProvider defaults
src/kiro_crew/dashboard/, src/kiro_crew/context_assembly/, src/kiro_crew/context.py, src/kiro_crew/slack/ steer_retract.py, the route and its coded refusals, the retractable row mark, the settle and teardown hooks, the settle comparing each steer's escaped text (steer_settle.py); a cold start's replay, an interrupted or stopped turn's restore and Regenerate step past a taken-back steer; the stopped-turn restore reads whole rows, in a worker thread on the dashboard and in Slack
website/src/pages/, website/src/hooks/, website/src/api/, website/src/store/, website/src/utils/ the controls, the collapse into the note, the note's buttons and the ErrorNotice in UserMessage.tsx (refusals and a failed request); useSteerRetract.ts and its per-chat text, kept until the message box's own draft write is known to have stuck (slotDraftStore.save and the composer's saveDrafts report whether a write stuck); retractSteer; retracted as a settled state; the pinned prompt skipping it
website/src/i18n/ twelve strings in all twelve language catalogs and the pseudolocale, with translator context
scripts/, docs/ check_harness_parity.py requires the new set's live capture; the steer contract in learn-cron-dashboard.md, the H6 row, the onboarding table, a feature-map row

Backwards compatibility

Compatible: the change adds a route, a fourth steer state and optional row meta. Every existing steer state, route and row keeps its shape, and a steer on any harness without the request is drawn exactly as before. On kiro-cli and KAS, a steer whose text holds <user_message> or </user_message> reaches the agent with those tags written as &lt;user_message&gt; and &lt;/user_message&gt;, so it cannot close the wrapper the agent reads; the other harnesses receive the text as typed, and the chat shows it as typed.

Removes nothing: no key, route, state or reader goes away; steerState: retracted is written only by a take-back.

Tests

  • test/test_steer_retract.py: the retractable mark (composer steer on a capable harness only, not a peer's); the route's refusals and their codes (unknown or non-string action, no longer pending, busy while a steer or another take-back is in flight, an app, whose refusal writes one denied SEL row, a refused clear withdrawing the offer, and a clear refused after the turn already settled the take-back, by ending or by reading the steer, answering accepted, withdrawing nothing and leaving the next take-back free); Queue instead keeping the audience it had when the person asked, whether kiro-cli reports the drop or the turn ends first, so a mirror added in between makes the drain drop the queued entry, and one with nothing changed draining as usual; a reported drop taking back Unsend (row text cleared, on the row and the frame), Edit and Queue instead (with attachments), and resending the other pending steers in order but not one sent after the clear; the turn reading the steer first; a read steer whose text holds </user_message> that can be neither taken back nor sent again; the turn ending first (Unsend not requeued, Queue instead left to the requeue); a new steer held at the clear's gate until its answer, under bounded waits, and handed back to the caller's queue, with no row written, when its turn ends or is hard-stopped while it waits; a take-back from an earlier turn blocking nothing; a row a stopped turn left written never taking back a newer steer with the same text; the session provider passing the offer and the clear through to its handle; the turn's event loop handing a drop to the take-back; a hard stop releasing an armed Unsend, Edit or Queue instead without touching its row, since the runner may not yet have processed kiro-cli's report that the turn read the steer (a late report leaves the row as it was), discarding it with the other pending steers, and releasing a steer held for the clear; the next turn withdrawing the offer a hard stop or a restart left on a row, but not while a steer is pending or either half of a take-back is armed; a steer a recovery or an ambiguous write marked possibly delivered: an armed take-back on it ends, a clear's drop neither takes it back nor resends it, and the turn end leaves it to the requeue, which runs it with the possibly-delivered note; two steers whose escaped keys collide, with an echo of one: both are possibly delivered, so neither can be taken back, a take-back armed on one ends, a clear's drop resends neither, and the turn end requeues both with the note.
  • test/test_steer_settle.py: escape_user_message_markup and wrap_user_message on text with either tag, neither, and the escape text itself; a steer holding either tag settles from its escaped envelope, two identical ones settle one at a time, one the echo does not carry stays pending, and a steer quoting another's envelope settles only itself; steers whose text copies other steers' envelopes, one or two of them, get echoes that name exactly what was injected; and an exhaustive check over every order of four such steers, every set kiro-cli could have injected and three separators settles exactly the injected ones; possibly_consumed_steers returns a collision group the echo covers only in part, and nothing for one it covers fully, not at all, or whose texts are identical.
  • The steer writes of kiro-cli, KAS, codex, claude, goose, opencode and pi (test_acp_client_more_coverage_session_frames.py, test_acp_runtime_client_parity.py, test_codex_steering_request.py, test_claude_steering_request.py, test_goose_steer_request.py, test_opencode_concurrent_prompt_steer.py, test_pi_steer.py) send the escaped envelope for text holding </user_message>.
  • test/test_regenerate_taken_back_steer.py: Regenerate steps past a taken-back steer to the prompt (with and without a reply after it), never crosses the request above it, and still takes a steer the turn read as the request. test/test_history_restore_preambles.py: a stopped turn's restore names the prompt, not a taken-back steer.
  • test/test_providers_base_cov80.py: a provider that does not opt in offers no take-back and sends nothing.
  • test/test_steer_clear_handle.py: clear_pending_steers through the real reader loop (the request and its answer, a refusal that stops the offer and sends nothing again, no answer in time, KAS never sent it), and the turn yielding EVENT_STEER_DROPPED.
  • The two new captures replay through test_acp_frame_replay.py; test_harness_parity.py holds the handle's answer to the table for every backend.
  • test_interrupted_turn_steers.py: a taken-back steer is not carried into an interrupted turn's restore, and a cold start's build_session_replay leaves it out, from disk and from the live window.
  • Frontend: UserMessage.steerTakeBack.test.tsx (the two controls and the menu, when they show, rendered through the shared Btn, the status and its explainer kept apart from the two controls, the progress line and its icon, refusals as an ErrorNotice, a failed request's notice under the progress line until the row settles, the note; the row keeps one element from pending to taken back, a row read back taken back is drawn without the collapse, the Edit and Queue instead notes are buttons to the message box and the Unsend note is not), useSteerRetract.test.ts (the outcome read from the row; Edit restoring the message box and moving the cursor there, with a quoted steer's quote block kept in the restored text so the saved draft holds the whole message; a hard stop handing Edit and Queue instead back; the text kept through a failed or unanswered request, through leaving the chat and through a reload, with the stored copy removed once the row settles and a corrupt or expired one dropped; a network error or a 503 keeping the take-back pending and marked as failed until it settles; a page that has not seen the turn running restoring nothing; a missing row settling, an Edit's text going back to the message box first and Unsend's not, and a missing row or an ended turn changing nothing while the chat's history is still loading; one chat's take-back never touching another's; an Edit whose copy cannot be stored never sent and shown as failed, while Unsend and Queue instead still go; a restore whose draft write fails keeping the stored copy, restoring it once per page and again after a reload; another tab's take-back on the same chat that has not settled, or one clicked at the same moment for the same steer or another, refusing the new one as busy, sending nothing and keeping the stored copy, which a reload of the first tab restores), slotDraftStore.test.ts (save reports whether the write stuck), transcriptRowCallbacksStable.test.tsx and CrewChatWindow.test.tsx (the page's Regenerate steps past a taken-back steer, with and without a later reply, as the server does), ChatPageDrafts.test.tsx (Edit restored on switching back into a chat, from its cache or after a reload, even when the reply streams before the history arrives, lands in that chat's draft and files and never carries another chat's text, and a draft write that fails leaves the stored copy in place), pinnedPrompt.retractedSteer.test.ts (never pinned, never the next prompt), the stale-snapshot merge keeping retracted, and the steer_push frame trace carrying the mark.
  • Determinism (D12): the new or changed backend tests, with the route, slot and session-handle tables they extend (479 tests), passed 20 runs at -n0, 10 runs at -n 4 --dist loadgroup --max-worker-restart=2 beside test_steer_requeue.py and test_side_steer_queue.py, and a shuffled order (seed 67181). The ten frontend test files this change touches passed 8 shuffled orders (--sequence.shuffle, seeds 1, 3, 11, 222, 777, 4242, 31337, 65535).
  • Each of the 81 hunks in the Python files this change modifies was reverted alone (the new modules have their own tests): 43 make a test fail (one with a KeyError on the frame field it adds), 20 remove a name other code uses and fail with an import, name or attribute error instead of an assertion (9 before any test runs), and the 18 that pass are docstrings, imports and re-exports (an undefined name fails flake8), a type annotation, a capability description that test_backend_cards.py checks, the parity gate's own registry row (without it the gate stops checking the set's capture), and the two calls that run the stopped-turn restore in a worker thread, which no test can tell from a direct call.

Manual verification

Against a real kiro-cli 2.33.1, with Crew's own AcpRuntime, AcpSessionHandle and the dashboard's steer_into_running_turn and take-back handlers: a turn ran sleep 20, two steers were sent ("end with pineapple", "start with mango"), and the first was taken back with Edit. Its row became retracted, the second was resent and obeyed ("Mango: the command printed first-done."), the answer had no pineapple, and nothing was queued. The full path was then driven from the browser: the recording below is a scratch gateway on this branch with a real kiro-cli 2.33.1 turn running sleep 75, and three steers taken back from the dashboard.

Screenshots / video

The full flow, from a running turn: Queue instead (the note, and the message in the queue above the message box), Edit from the menu (the note, and the text back in the message box with the cursor on it), and Unsend.

recorded from b28eaf962a9dde544e52bb594e90bf87493fce0e (before this head's coded refusals, the read-steer parser fix, the failed-request notice, the Regenerate fixes, the reload-safe Edit and the divider between the status and the two controls; the steps shown are the same, and the stills below show the row as it is now) · this branch on origin/main 5cc82ba, dashboard served from its own build · mode: KIROCREW_PROFILE=standalone, agent.acp_backend=kiro (kiro-cli 2.33.1), scratch data directory, cropped to the chat pane from the moment the shell tool runs · real server + model, no fixtures

steer-take-back.mp4

Queue instead, Edit and Unsend on three steers in one running kiro-cli turn

The real UserMessage with the built stylesheet and the English catalog, in each state: pending with Queue instead and the open menu, a take-back in flight, one whose request failed, a refused one, a steer the turn read first, and the three notes.

Dark theme: the take-back controls and every state

Light theme: the same states

Related Issues

Closes #18190
Closes #16300

Checklist

  • At most two commits (one is the norm), with a Conventional Commits title (feat|fix|docs|style|refactor|perf|test|chore|ci|build|revert: ...)
  • Existing tests pass and new tests added for new functionality
  • Self-review completed; code follows project style guidelines
  • Documentation updated (if applicable)
  • No secrets, credentials, or internal references in the diff

@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Design Review (Opus 5.5) — ✅ PASS

Design-level review of 5d0b4212e70e2b3fdf69c6036b674726c6c9bfd1 — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS does not.

Design-Verdict: PASS

Nothing blocks: the take-back is opt-in per harness, the outcome comes from what the turn reports rather than the clear's answer, and screenshots and recordings of this revision are attached.

Notes

  • Writing <user_message> as &lt;user_message&gt; on kiro-cli and KAS changes what the agent receives for every steer, not only ones that get taken back. It deserves its own commit or PR so it can be reverted without losing the take-back feature.
  • A steer sent after CLEAR_GATE_WAIT_SECS runs out is not in the clear's batch. If that late clear drops it, only the turn's teardown recovers it. Pin that path with a test.

[DESIGN-REVIEWED] 5d0b421

Description read

[DESCRIPTION-READ] 294c09827cd48f583dfb2addc4b9b33b2e72cf146c2d448180e415f15f6b03ea

That sha256 names everything this verdict read: the PR description, plus the 1 evidence file(s) the workflow collected from it and handed this review. It covers that evidence because the media strip replaces every attachment URL with the same placeholder, so an edit swapping one attachment for another would leave the description's captured text identical while what this review judged changed. Recompute it with .github/scripts/pr-description-capture.sh over the same inputs; if it differs, the description or that evidence changed after this verdict was formed and any finding drawn from either is unproven.

@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

First Principles Review (Opus 5.5) — ✅ PASS

Premise-level review of 5d0b4212e70e2b3fdf69c6036b674726c6c9bfd1 — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push. A BLOCK verdict blocks PR readiness; PASS does not.

First-Principles-Verdict: PASS

Nothing blocks. One note: the description's compatibility claim about which harnesses get escaped steer text does not match the diff.

Notes

  • Item 5 — undeclared: the description says "On kiro-cli and KAS… the other harnesses receive the text as typed", but _steer_via_concurrent_prompt (opencode) and pi_steer_turn.steer (pi) now send wrap_user_message(text). test_opencode_concurrent_prompt_steer.py pins _WRAPPED = "<user_message>\nend &lt;/user_message&gt; …" going over the wire, so those two harnesses also get the escaped text. Subtraction: use the plain wrapper again on those two paths, or narrow the compatibility sentence to match the diff.

What this change ships

Inventory (8 items) — 7 justified

Intent: let a person take back a steer the running turn has not read yet, by Unsend, Edit, or Queue instead. ADDITION.

  1. A pending kiro-cli steer shows "Queue instead" next to "Steering…", with Edit and Unsend in a menu — justified
  2. A taken-back steer collapses into a note in its place; its text goes to the message box, the queue, or nowhere — justified
  3. The other pending steers are sent again in order after a clear — justified
  4. A taken-back row loses its text, and replay, restore, Regenerate and the pinned prompt skip it — justified
  5. Steer text holding <user_message> is escaped before the agent sees it (kiro-cli, KAS, opencode, pi) — undeclared
  6. A new harness capability set ACP_BACKENDS_STEER_RETRACT (kiro-cli only), read through supports_steer_retract — justified
  7. A failed take-back request shows an error under the progress line; refusals carry codes — justified
  8. A restored queued-card draft is now saved straight away, so it survives a reload — justified

[FIRST-PRINCIPLES-REVIEWED] 5d0b421

Description read

[DESCRIPTION-READ] 1da02eb3232d0acf83953c73e9b4b5cf8c81b1bb3c869bf119a78249e07add9f

That sha256 names the PR description this verdict read. Recompute it with .github/scripts/pr-description-capture.sh; if it differs, the description changed after this verdict was formed and any finding drawn from the description is unproven.

@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

UX Review (Opus 5.5) — ✅ PASS

UX-level review of 5d0b4212e70e2b3fdf69c6036b674726c6c9bfd1 — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS does not.

UX-Verdict: PASS

Nothing blocks. Screenshots cover every new control and state (dark and light), and the reader read Queue instead, Edit and Unsend correctly. A recording is supplied for the bubble-to-note collapse.

Notes

  • steer_take_back_unconfirmed is drawn as a red error, but the request may still succeed. The reader would "just wait, worried" and asked "whether the assistant will still act on my message". Render it muted and reword it, e.g. "Not confirmed yet — this updates when the agent reads your message."
  • steer_take_back_busy ("Another message is still on its way") left the reader unsure "which 'another message' it means". Name it: "Your earlier message is still being sent. Try again in a moment."
  • The menu says "Edit", but the result says "You moved this message back to your message box". The reader "didn't expect 'Edit' to mean take the message out and put it back in my typing box". Rename the item "Edit in message box" so the label matches what it does.

[UX-REVIEWED] 5d0b421

Description read

[DESCRIPTION-READ] 1b0fac04d5644c0208d07651d11967a912e8e77c2d5020620e5652ede9e37dad

That sha256 names everything this verdict read: the PR description, plus the 6 evidence file(s) the workflow collected from it and handed this review. It covers that evidence because the media strip replaces every attachment URL with the same placeholder, so an edit swapping one attachment for another would leave the description's captured text identical while what this review judged changed. Recompute it with .github/scripts/pr-description-capture.sh over the same inputs; if it differs, the description or that evidence changed after this verdict was formed and any finding drawn from either is unproven.

@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Opus 5.5 Review — ✅ no blocking findings

Reviewed 5d0b4212e70e2b3fdf69c6036b674726c6c9bfd1 — this comment is updated in place on each push.

Review details

No findings.

[OPUS-REVIEWED] 5d0b421

Verdict parsed from the review's SHA-scoped output markers for commit 5d0b4212e70e2b3fdf69c6036b674726c6c9bfd1.

False positive or not applicable? A repository writer can comment:
/ai-review override fable 5d0b4212e70e2b3fdf69c6036b674726c6c9bfd1 false-positive: <reason, 20 to 500 characters>

@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

GPT 6.1 Review — 🔴 changes requested (blocking)

GPT 6.1 found at least one blocking issue that must be resolved before merging 5d0b4212e70e2b3fdf69c6036b674726c6c9bfd1. 1 of 2 blocking finding(s) are security-class and were withheld from adjudication, so the blocking verdict stands.

This comment is updated in place on each push.

BLOCKING -- website/src/hooks/useSteerRetract.ts:139 -- Different-chat locks allow recovery records to overwrite each other

void locks.request(${TAKE_BACKS_STORAGE_KEY}:${slot}, callback)
Trigger: Two tabs start Edit take-backs for different chats concurrently. Their distinct locks let persistTakeBack read the same shared recovery blob and then save competing whole-store snapshots.
Outcome: The later write erases the other chat’s recovery record. Once the server clears that steer’s transcript text, reloading its tab before composer restoration permanently loses the text.
Anchor: residual/crash-data-loss-corruption
Fix: Store recovery records separately per chat, or serialize every shared recovery-store read-modify-write under one store-wide lock.

BLOCKING -- website/src/pages/chat/page/composerDrafts.ts:61 -- Partial persistence duplicates restored text after reload

return textStored && filesStored
Trigger: Edit restores an attached steer into a nonempty draft; the text write succeeds, but the enlarged file draft exceeds sessionStorage quota. restoreAndSettle retains the complete recovery record because saveDrafts returns false. Reloading loads both that record and the already-restored text.
Outcome: mergeRecoveredDraft compares the combined draft with the original recovery payload and appends the payload again. Each reload duplicates the restored text while file persistence continues failing.
Anchor: defect
Fix: Persist restoration progress separately for text and files, so retrying an unsuccessful file write does not replay text already stored.

[BLOCK-MERGE] 5d0b421
[GPT-REVIEWED] 5d0b421

Adjudication (Opus 5.5) — is blocking on each finding proportionate?

Rulings: F2 is downgraded so it no longer blocks. F1 stays blocking because it is fenced, but I flagged it with a draft override rationale. Both rest on code I opened in this run.

F2 — LOW. The record only fails to clear when the text write succeeds and the file-draft write fails (composerDrafts.ts:56-61). That is a list of file paths running out of sessionStorage quota. The record is then kept (useSteerRetract.ts:289-292), and the "already tried" memory resets on reload (useSteerRetract.ts:74, :202). So each reload adds the text again (busyTurnControls.ts:285, chatDrafts.ts:117-120). The user sees a duplicate in the composer and can delete it; the text is never lost, which is the trade-off the code chooses on purpose (useSteerRetract.ts:362-364, chatDrafts.ts:109-110). The real fix means saving restore progress separately for text and files, which is new persisted state on every recovery site. That costs more than this rare, visible, harmless duplication.

F1 (fenced) — Two tabs would each have to read and then write the shared store at the same moment. Each tab's read and write run in one synchronous step (persistTakeBack, useSteerRetract.ts:180-185), so that window is microseconds. Even if one record is overwritten, the tab that started it keeps its copy in memory (useSteerRetract.ts:181, :199-200) and still restores the text. Text is only lost if that tab also reloads before the server reports the outcome.

[ADJUDICATION] 5d0b4212e70e2b3fdf69c6036b674726c6c9bfd1 total=1 uphold=0 downgrade=1
DOWNGRADE F2 website/src/pages/chat/page/composerDrafts.ts:61 reason=disproportionate-remedy
[GPT-ADJUDICATED] 5d0b4212e70e2b3fdf69c6036b674726c6c9bfd1

[ADJUDICATION-FENCED] 5d0b4212e70e2b3fdf69c6036b674726c6c9bfd1 fenced=1 flagged=1
FLAG F1 website/src/hooks/useSteerRetract.ts:139 -- Loss requires two tabs' synchronous read-then-write of the shared store (useSteerRetract.ts:182-184) to overlap within microseconds, and then the tab whose record was overwritten must reload before the outcome arrives, because until then its in-memory copy (useSteerRetract.ts:181, :199-200) still restores the text.
[GPT-ADJUDICATED-FENCED] 5d0b4212e70e2b3fdf69c6036b674726c6c9bfd1

🏷️ Fenced finding(s) machine-flagged as likely edge case

The security fence keeps these findings blocking regardless of adjudication; the only clearance path is a human override. The annotate-only pass judged the condition combination each one requires extreme, and pre-drafted the override rationale below. A repository writer must independently verify a rationale before posting it — it is machine-authored, and a wrong override on a security-class finding ships exactly the class the fence exists to stop.

  • F1 website/src/hooks/useSteerRetract.ts:139 — Loss requires two tabs' synchronous read-then-write of the shared store (useSteerRetract.ts:182-184) to overlap within microseconds, and then the tab whose record was overwritten must reload before the outcome arrives, because until then its in-memory copy (useSteerRetract.ts:181, :199-200) still restores the text.

    /ai-review override gpt 5d0b4212e70e2b3fdf69c6036b674726c6c9bfd1 <class>: Loss requires two tabs' synchronous read-then-write of the shared store (useSteerRetract.ts:182-184) to overlap within microseconds, and then the tab whose record was overwritten must reload before the outcome arrives, because until then its in-memory copy (useSteerRetract.ts:181, :199-200) still restores the text.
    

Replace <class> with the class that records what you did. These findings are real, so for an accepted risk use owner-decision (link the decision comment in the reason) or deferred #<N> (a tracked follow-up issue); false-positive only if you verified the finding is wrong. Not not-applicable: that class files the override as a rule mismatch in the audit.

False positive or not applicable? A repository writer can comment:
/ai-review override gpt 5d0b4212e70e2b3fdf69c6036b674726c6c9bfd1 false-positive: <reason, 20 to 500 characters>

@github-actions github-actions Bot added the readiness: action required A blocking check or review needs attention label Oct 23, 2026
@unstablebrainiac
unstablebrainiac force-pushed the unstablebrainiac/feat/pending-steer-actions branch from 90c0d5e to b876cf4 Compare October 23, 2026 10:26
@unstablebrainiac
unstablebrainiac force-pushed the unstablebrainiac/feat/pending-steer-actions branch from b876cf4 to 00c19ba Compare October 23, 2026 13:04
@github-actions github-actions Bot added the merge conflict Branch has merge conflicts with its base — author must resolve before merge label Oct 23, 2026
@unstablebrainiac
unstablebrainiac force-pushed the unstablebrainiac/feat/pending-steer-actions branch from 00c19ba to b3fb295 Compare October 23, 2026 15:48
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention merge conflict Branch has merge conflicts with its base — author must resolve before merge labels Oct 23, 2026
@unstablebrainiac
unstablebrainiac force-pushed the unstablebrainiac/feat/pending-steer-actions branch from b3fb295 to 478c7ac Compare October 23, 2026 16:38
@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention merge conflict Branch has merge conflicts with its base — author must resolve before merge and removed readiness: checking Automated validation is still running labels Oct 23, 2026
@unstablebrainiac
unstablebrainiac force-pushed the unstablebrainiac/feat/pending-steer-actions branch from 478c7ac to c6010cf Compare October 23, 2026 20:31
@unstablebrainiac
unstablebrainiac force-pushed the unstablebrainiac/feat/pending-steer-actions branch from 3edfb8d to 3a5cf40 Compare October 24, 2026 04:45
@unstablebrainiac
unstablebrainiac force-pushed the unstablebrainiac/feat/pending-steer-actions branch from 3a5cf40 to 5d569f5 Compare October 24, 2026 05:45
@github-actions github-actions Bot added readiness: checking Automated validation is still running merge conflict Branch has merge conflicts with its base — author must resolve before merge and removed readiness: action required A blocking check or review needs attention labels Oct 24, 2026
@unstablebrainiac
unstablebrainiac force-pushed the unstablebrainiac/feat/pending-steer-actions branch from 5d569f5 to 20316a1 Compare October 24, 2026 07:12
@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed merge conflict Branch has merge conflicts with its base — author must resolve before merge readiness: checking Automated validation is still running labels Oct 24, 2026
@unstablebrainiac
unstablebrainiac force-pushed the unstablebrainiac/feat/pending-steer-actions branch from 20316a1 to 53202be Compare October 24, 2026 08:35
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Oct 24, 2026
@unstablebrainiac
unstablebrainiac force-pushed the unstablebrainiac/feat/pending-steer-actions branch from 53202be to 09cc563 Compare October 24, 2026 09:51
A pending steer that can be taken back now shows Unsend, Edit and Queue
instead beside "Steering…". kiro-cli 2.33's `_session/steer/clear` drops the
steers it still holds and reports them with AgentExecutionUserMessageCleared,
which the turn yields as EVENT_STEER_DROPPED. The outcome is read from what the
turn reports after the clear, never from the clear's own answer: a reported drop
takes the steer back (the row becomes `steerState: retracted`), a consumption
echo first means the turn read it, and a turn that ends first never took it.
Edit hands the text back to the composer, Queue instead puts it in the queue.

The clear drops every pending steer, so the others are sent again in order, and
a steer sent while a clear is in flight waits for its answer. The capability is
opt-in per harness (ACP_BACKENDS_STEER_RETRACT, kiro-cli only, on two live
captures); KAS was measured and left out because its clear names steers its turn
already read.

Closes #18190
Closes #16300
@unstablebrainiac
unstablebrainiac force-pushed the unstablebrainiac/feat/pending-steer-actions branch from 09cc563 to 5d0b421 Compare October 24, 2026 11:03

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

readiness: action required A blocking check or review needs attention

Projects

None yet

1 participant