Skip to content

feat(cli): pause a subagent's task when the user interrupts it - #14752

Open
calebnorman wants to merge 4 commits into
Kilo-Org:mainfrom
calebnorman:subagent-interrupt-pauses-task
Open

calebnorman wants to merge 4 commits into
Kilo-Org:mainfrom
calebnorman:subagent-interrupt-pauses-task

Conversation

@calebnorman

@calebnorman calebnorman commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Follows #14701 (double Esc in a subagent view) and #14702 (steering a subagent from its view), both now in main.

What

Interrupting a subagent now pauses its task instead of ending it, so the TUI matches VS Code:

  • Interrupt (Esc in any session view) uses scope: "session". It stops that session's turn and its foreground descendants, and background descendants keep running. This covers the TUI prompt's double-Esc and feat(cli): interrupt a subagent from its view with double Esc #14701's subagent-view double-Esc; both used to send tree.
  • When the interrupted session is a subagent, its task's job stays running and runTask waits in a Kilo-owned pause (src/kilocode/tool/task-pause.ts). A foreground parent stays blocked in its task call, and a background parent's result stays pending.
  • The next prompt to the paused subagent resumes the task. That's either a steering prompt from the subagent view or a parent task_id call. The task then waits for that turn and delivers its result normally. If that turn is interrupted too, the task pauses again.
  • Cancel is unchanged: a tree abort on the subagent (the VS Code task card's or agent bar's Stop) is the only way to end a paused task early. Session delete and instance dispose also tear the pause down. Parent Esc and Ctrl+B promotion behave as before.

A foreground task is meant to work until it completes, so nothing returns early with a partial or "interrupted" result.

Why

Before this change, an interrupt either hung the parent or ended the task as a failure. It hung because the task waited on the child's drain, which also holds for each background grandchild. It failed because the turn's AbortedError surfaced as the tool result. The TUI also sent tree from Esc, which cancelled background work that VS Code keeps running.

Notes for review

  • Shared-file footprint. The pause logic lives in Kilo files. src/tool/task.ts keeps the upstream runTask shape and calls KiloTaskPause.settle, and KiloTaskPause.extend wraps background.extend for task_id. src/session/prompt.ts calls a single KiloSessionAdmission.admit hook. The TUI subagent-view keys live in feat(cli): interrupt a subagent from its view with double Esc #14701's useSubagentKeys, and the task card and footer read the pause through kilocode/task-pause-view.tsx. feat(cli): interrupt a subagent from its view with double Esc #14701's tree-cancel test moved from test/session/prompt.test.ts to test/kilocode/task-pause.test.ts.

  • Pause condition. A run pauses only when the child's latest assistant message ended with an AbortedError, the child is paused in KiloSessionControl, and the job is still running. AbortedError has other sources (provider aborts, direct SessionRunState.cancel, dispose), and only a user stop sets paused.

  • Visibility. The pause is exposed on the child session's existing metadata record as "kilo.task": { status: "paused" }. It's projected from the live registry through the GoalState.project hook on every session read and update, so a marker left by a crashed process is never shown. There's no new endpoint or event, and no change to BackgroundJob or the session status schemas.

  • TUI display.

    • The subagent footer shows "Interrupted · send a prompt to resume", and the steering prompt stays open while the task is paused.
    • The parent task card shows "Interrupted — waiting for you" instead of a spinner. Foreground tasks also get a Ctrl+B hint.
  • Board notice. A paused background task, or a foreground task promoted while paused, posts a shared-board INFO to the parent using feat(tui): steer running subagents from their view #14702's posting path. Foreground pauses never post.

  • task_id on a paused job. background.extend would queue behind the pause forever, so the task tool sends the prompt to the child directly. Its admission resumes the paused run.

  • VS Code (not changed here). WorkingIndicator's cancel-retry sends scope: "session" to the current session. In the read-only sidebar sub-agent viewer, that session can be a child, so clicking it now pauses the task. VS Code has no steering prompt in that view yet.

)

// the parent was not aborted: it finished its step and made its next model call
expect(result.parts.some((part) => part.type === "text" && part.text === "parent recovered")).toBe(true)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move all kilo specific code to kilo files and code. The goal should be to reduce kilo change markers as much as possible. We also have a skill for that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved code into our managed files.

/** Child session metadata key that marks a task paused by the user. Must match the TUI. */
export const KEY = "kilo.task"

export const NOTICE =

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a bit unsure what this is for, @kilocode-bot does this introduce a new tool and why is it needed for this change?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it doesn't add a tool. task-pause.ts is a helper module for the existing task tool. It sits under src/kilocode/tool/ because runTask uses it. That placement is from the PR description and the file header. I haven't read the rest of the file.

What it does: it's a registry of paused tasks, keyed by child session ID. When a user interrupts a subagent, runTask waits in this registry instead of returning. The next prompt to that child resumes the wait, and the task then waits on that new turn and delivers its result normally.

Why it's needed:

  • Before this change, an interrupt either hung the parent or ended the task as a failure. It hung because the task waited on the child's drain, which also holds for each background grandchild. It failed because the turn's AbortedError became the tool result.
  • The goal is for Esc on a subagent to pause the task, as in VS Code, not end it. The task's job stays running, and the parent stays blocked or pending.
  • Only a tree abort of the child (Cancel), session delete, or instance dispose ends a paused task early.
  • Keeping this logic in a separate Kilo-owned file also keeps the changes to upstream task tool code small, which I assume is the usual reason for the src/kilocode/ split.

Related pieces in the PR:

  • The registry is projected into the child session's metadata as kilo.task: { status: paused } through the GoalState.project hook. That's how the TUI shows "Interrupted · send a prompt to resume".
  • The registry is live, so a marker left behind by a crashed process is never shown as paused.

I can look at specific functions in the file if you tell me which part is unclear.

Caleb Norman added 2 commits October 2, 2026 15:26
An interrupt (session-scope abort) of a subagent stops its turn but keeps the
task's job running: the task waits in a Kilo-owned pause until the next prompt
to the child resumes it. Only a tree cancel, delete or dispose ends it early.
The pause is exposed on the child session's metadata, and a paused background
task posts a board notice to its parent. The subagent tree-cancel test moves
into test/kilocode.
Esc in the prompt and double Esc in a subagent view now interrupt with
session scope. A paused subagent keeps its steering prompt open to resume the
task, its footer says so, and the parent's task card shows the task waiting
instead of a spinner.
@calebnorman
calebnorman force-pushed the subagent-interrupt-pauses-task branch from 691a461 to f865790 Compare October 2, 2026 19:33
@calebnorman
calebnorman marked this pull request as ready for review October 2, 2026 19:33
@calebnorman

Copy link
Copy Markdown
Contributor Author

Adversarial review

The overall design works: the pause logic sits in Kilo files, the shared-file footprint is small and annotated, and the tests drive the real runtime with no mocks. I checked the admission race first. It's fine: prompt runs inside drain.track, so drain.wait after resume can't return before the new turn starts. Issues, most important first:

1. Detached leaks into the resumed child turn (correctness)

KiloTaskPause.extend provides Detached=true to all of runTask(). That includes ops.prompt, which runs EffectBridge.make(), and that captures the full Effect.context() for the child's loop. So every tool in that follow-up turn appears to see Detached=true, including nested task calls. Any foreground grandchild started during a task_id-resumed turn then hits if (yield* Detached) return input.initial in settle. That skips drain.wait, so the result can come back before its background descendants finish, and it also skips the pause. If the user interrupts that grandchild, the task fails with MessageAbortedError instead of pausing. Fix: scope the flag to the outer settle only. For example, pass it as an argument or runTask({ detached: true }) instead of an ambient Reference, or reset it to false around ops.prompt. Add a test that interrupts a grandchild started by a task_id-resumed turn.

2. Window where task_id can still deadlock

extend checks pauses.has(id), but the entry only exists once settle's hold has run. Between the abort and that point there are several awaits: the messages read, ops.paused, and jobs.get. If a parent task_id arrives in that window, it goes to jobs.extend and queues behind a run that is about to pause indefinitely. The parent is told "Background task updated", but the follow-up prompt never runs until a human steers. Consider also branching on KiloSessionControl.paused(child) with the job running, or registering the entry before the checks.

3. Behavior change for non-TUI clients (regression risk)

Any scope: "session" abort on a child session now leaves a foreground parent blocked with no time limit. The PR notes that VS Code's read-only sub-agent viewer sends exactly this, and that view has no way to resume. Today a click there "stops" the subagent; after this PR the parent hangs until someone finds the task-card Stop. Either gate the pause on a client signal, or ship the VS Code resume/indicator in the same release. The SDK description for scope=session (httpapi/groups/session.ts:61) should also mention that it pauses the parent's task. As written, it describes the old semantics and will mislead SDK consumers.

4. Repo practices / smaller items

  • pauses is a module-global Map, while the related KiloSessionControl state uses InstanceState. It works because teardown runs through fiber interruption, but it's the one piece of pause state that isn't scoped to an instance. Prefer InstanceState for consistency on the shared multi-directory backend.
  • The forkDetach in extend isn't tied to the parent's tool abort or to instance scope. A parent Esc during that call won't stop the delivered prompt.
  • GoalState.project now imports tool/task-pause, which pulls in steering, then board, then the DB. That's an odd dependency direction for goal state. A small projection registry, or calling both projectors at the call site, would keep them decoupled.
  • In settle, let state + while (true) goes against the style guide's "avoid let". A recursive Effect.suspend loop reads just as clearly.
  • KiloSessionSteering.post is now a generic "subagent board notice" helper but still lives in steering.ts, and its log message changed from "steering notification failed" to "board notice failed". That's minor, but it loses grep-ability for existing steering logs.

Verdict: #1 and #2 should be fixed before merge. #3 needs an explicit decision: either ship it alongside the VS Code change, or document the hang risk.

Effect.acquireUseRelease(
Effect.gen(function* () {
const entry = { done: yield* Deferred.make<void>(), notice: notice(messageID) }
pauses.set(input.child, entry)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Potential registry leak / permanently stuck "paused" state

pauses.set(input.child, entry) registers the pause before the fallible publish/entry.notice work in the acquire phase. Effect.acquireUseRelease only runs the release (which deletes the entry) when acquire succeeds, so if entry.notice raises a defect (e.g. a DB defect from BoardStore.post that escapes the typed Effect.catch in KiloSessionSteering.post), acquire fails, the entry is never removed, and the child keeps exposing kilo.task: { status: "paused" } forever while nothing can resume it. Consider catchCause-ing the notice or making the removal unconditional.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

if (!pauses.has(input.id)) return yield* jobs.extend(input)
yield* direct.pipe(
Effect.provideService(Detached, true),
Effect.catchCause((cause) => Effect.logWarning("paused task prompt failed", { cause })),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: A failed prompt to a paused task is swallowed and reported as success

In the paused branch direct is forked, every cause is caught here and only logged, and extend still returns true. The caller then reports "Additional context sent to the running background task". But if ops.prompt fails before KiloSessionAdmission.admit runs (e.g. a session lookup error), resume is never called and the task stays paused indefinitely, with no error surfaced to the parent or user. Consider propagating a failure (or a non-success result) so the caller does not claim the prompt was delivered.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

yield* run.prompt.cancel(run.child, "session")
yield* paused(run.child)
const board = yield* BoardStore.read({ sessionID: run.chat.id })
expect(board.messages).toEqual([

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: This assertion can race the pause registration

paused() polls KiloTaskPause.paused(), which becomes true as soon as pauses.set runs in hold's acquire, before the board notice is posted. The BoardStore.read immediately after can therefore observe an empty board and flake under load. Poll the board until the notice arrives instead of using the in-memory registry as a barrier. The same pattern appears at line 697 (asserting two notices).


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

expect(row).not.toContain("⋯")
expect(row).toContain("Parent up")
// a narrow footer drops prev/next while the paused hint shows
expect(row?.includes("Next right")).toBe(width >= 96)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Width threshold does not match the component

subagent-footer.tsx computes narrow = width - 4 < 96, so prev/next are hidden for width < 100, but this asserts they are visible for width >= 96. It passes today only because the loop tests 80 and 120; a width of 96-99 would fail a correct implementation. Derive the expectation from the same condition (or from the rendered compact behavior).


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
packages/kilo-vscode/webview-ui/src/components/shared/working-indicator-utils.ts 32 cancelScope maps an unknown parentID to "session"; the subagent viewer sets the current session before its record loads, so Cancel in that window pauses the parent task instead of cancelling the subagent
Files Reviewed (6 files)
  • packages/opencode/src/kilocode/session/admission.ts
  • packages/opencode/src/kilocode/tool/task-pause-state.ts
  • packages/opencode/src/kilocode/tool/task-pause.ts
  • packages/opencode/test/kilocode/task-pause.test.ts
  • packages/tui/test/kilocode/subagent-view.test.tsx
  • packages/kilo-vscode/webview-ui/src/components/shared/working-indicator-utils.ts - 1 issue

Fix these issues in Kilo Cloud

Previous Review Summaries (2 snapshots, latest commit 3715b64)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 3715b64)

Status: 5 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 4
SUGGESTION 1
Issue Details (click to expand)

WARNING

File Line Issue
packages/opencode/src/kilocode/tool/task-pause.ts 123 Pause-registry entry is registered inside the interruptible acquire phase, so an interrupt can leak it and leave the child reported as "paused"
packages/kilo-vscode/webview-ui/src/components/shared/working-indicator-utils.ts 32 cancelScope defaults an unknown parentID to "session", so Cancel in a not-yet-hydrated subagent view pauses the parent task instead of cancelling
packages/opencode/src/kilocode/tool/task-pause.ts 62 extend catches every cause of the detached prompt and still returns success, so a failed delivery resumes nothing while the parent is told it was sent
packages/opencode/test/kilocode/task-pause.test.ts 433 Board-notice assertion races the pause registration (same pattern at line 697)

SUGGESTION

File Line Issue
packages/tui/test/kilocode/subagent-view.test.tsx 471 Width assertion >= 96 disagrees with the component's narrow threshold (width - 4 < 96, i.e. hidden under 100)
Files Reviewed (18 files)
  • .changeset/vscode-subagent-cancel.md
  • packages/kilo-vscode/tests/unit/working-indicator.test.ts
  • packages/kilo-vscode/webview-ui/src/components/shared/WorkingIndicator.tsx
  • packages/kilo-vscode/webview-ui/src/components/shared/working-indicator-utils.ts - 1 issue
  • packages/opencode/src/kilocode/board/subagent-notice.ts
  • packages/opencode/src/kilocode/session/admission.ts
  • packages/opencode/src/kilocode/session/goal/state.ts
  • packages/opencode/src/kilocode/session/metadata.ts
  • packages/opencode/src/kilocode/session/steering.ts
  • packages/opencode/src/kilocode/tool/task-pause-state.ts
  • packages/opencode/src/kilocode/tool/task-pause.ts - 2 issues
  • packages/opencode/src/kilocode/tool/task.ts
  • packages/opencode/src/server/routes/instance/httpapi/groups/session.ts
  • packages/opencode/src/session/session.ts
  • packages/opencode/src/tool/task.ts
  • packages/opencode/test/kilocode/task-pause.test.ts - 1 issue
  • packages/sdk/js/src/v2/gen/types.gen.ts
  • packages/sdk/openapi.json

Fix these issues in Kilo Cloud

Previous review (commit f865790)

Status: 4 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 3
SUGGESTION 1
Issue Details (click to expand)

WARNING

File Line Issue
packages/opencode/src/kilocode/tool/task-pause.ts 129 pauses.set runs before fallible acquire work, so a defect in the board notice can leak the entry and leave the child stuck "paused"
packages/opencode/src/kilocode/tool/task-pause.ts 62 extend catches every cause of the detached prompt and still returns success, so a failed delivery resumes nothing while the parent is told it was sent
packages/opencode/test/kilocode/task-pause.test.ts 406 Board-notice assertion races the pause registration (same pattern at line 697)

SUGGESTION

File Line Issue
packages/tui/test/kilocode/subagent-view.test.tsx 471 Width assertion >= 96 disagrees with the component's narrow threshold (width - 4 < 96, i.e. hidden under 100)
Files Reviewed (20 files)
  • .changeset/subagent-interrupt-pauses-task.md
  • .changeset/tui-subagent-esc-interrupt.md
  • packages/opencode/src/kilocode/session/admission.ts
  • packages/opencode/src/kilocode/session/goal/state.ts
  • packages/opencode/src/kilocode/session/steering.ts
  • packages/opencode/src/kilocode/tool/task-pause.ts - 2 issues
  • packages/opencode/src/kilocode/tool/task.ts
  • packages/opencode/src/session/prompt.ts
  • packages/opencode/src/tool/task.ts
  • packages/opencode/test/kilocode/task-pause.test.ts - 1 issue
  • packages/opencode/test/session/prompt.test.ts
  • packages/tui/src/component/prompt/index.tsx
  • packages/tui/src/kilocode/steer.ts
  • packages/tui/src/kilocode/subagent-keys.ts
  • packages/tui/src/kilocode/task-pause-view.tsx
  • packages/tui/src/kilocode/task-pause.ts
  • packages/tui/src/routes/session/index.tsx
  • packages/tui/src/routes/session/subagent-footer.tsx
  • packages/tui/test/kilocode/steer.test.ts
  • packages/tui/test/kilocode/subagent-view.test.tsx - 1 issue

Fix these issues in Kilo Cloud


Reviewed by deepseek-v4.1-flash · Input: 0 · Output: 0 · Cached: 0

Review guidance: REVIEW.md from base branch main

…_id window

- Key the detached marker by child ID so a task_id-resumed turn's nested
  tasks still drain and pause.
- Take the direct path for a stopped child whose task has not paused yet,
  and resume a pause whose stop was cleared before it registered.
- Split the pause registry into a dependency-free module projected from
  session reads alongside goal state, move subagent board posting into
  its own module, and fork the delivered prompt in the instance scope.
- Cancel from a VS Code subagent view uses tree scope, since that view
  cannot resume a paused task; document the scopes in the SDK.
@calebnorman

Copy link
Copy Markdown
Contributor Author

All points are addressed in 3715b64.

1. Detached leak: fixed. The reference now holds the child session ID instead of a boolean, and settle only returns early when it matches its own child. The resumed turn still inherits the context, but a nested task's settle compares against its own (grandchild) ID, so it drains and pauses normally. I kept the reference rather than threading a flag through runTask so the shared task.ts stays at upstream shape. Test: "a grandchild interrupted during a task_id-resumed turn pauses its own task". It fails with the boolean marker.

2. task_id window: fixed on both sides.

  • KiloTaskPause.extend also takes the direct path when ops.paused(child) is true and the job is still running, so a task_id that lands before the pause is registered no longer queues behind it.
  • hold re-checks the user stop after registering. A prompt admitted between check and the registration already cleared the stop, so the pause resolves immediately instead of waiting for a resume that already ran.
  • Tests: "a task_id between the interrupt and the pause reaches the child instead of queueing" and "a prompt admitted while the task is entering its pause resumes it". Each fails without its fix.

3. Non-TUI clients. I shipped the VS Code change with this PR:

  • The working indicator's Cancel sends tree when the open session is a subagent and session otherwise. A subagent view can't resume a paused task, so a stop there is a Cancel under the product rule, and the parent continues with "Task cancelled by the user". This uses a cancelScope helper with unit tests, plus a kilo-code changeset.
  • The SDK scope description now says that tree on a subagent cancels the parent's task, and that session pauses it with no time limit until a new prompt to the subagent resumes it. The SDK is regenerated; only the description changed.
  • Other SDK clients that send session to a child get the documented pause.

4. Smaller items.

  • Registry: it's now task-pause-state.ts, which has no dependencies. It stays module-level rather than InstanceState because fromRow projects metadata synchronously and session IDs are globally unique. Each entry belongs to a settle fiber whose finalizer removes it, and instance dispose interrupts that fiber (the dispose test covers this). The file comment says this.
  • forkDetach: replaced with forkIn(scope) on the task tool's instance scope. The call returns immediately to a background task, so a later parent Esc correctly doesn't stop the delivered prompt; a tree cancel or job cancel still tears it down through the child runner.
  • Goal coupling: goal/state.ts is back to main. session.ts calls KiloSessionMetadata.project, which composes the goal and pause projections. That changes three already-marked lines and adds one import inside the existing Kilo import block.
  • let / while (true): replaced with a recursive Effect run.
  • Board posting: moved to board/subagent-notice.ts with a per-caller log label. steering.ts is back to its feat(tui): steer running subagents from their view #14702 shape apart from the call, and logs "subagent steering notification failed" again. The pause notice logs "subagent pause notice failed".

Checks:

  • packages/opencode: typecheck passes; 254 tests pass across the task-pause, prompt, task, steering, scoped-abort, goal, and board suites.
  • packages/tui: typecheck passes; 307 tests pass.
  • packages/kilo-vscode: typecheck, lint, knip, and check-kilocode-change pass; the working-indicator and session-dock tests pass.
  • The annotation check passes.

Effect.acquireUseRelease(
Effect.gen(function* () {
const entry = { done: yield* Deferred.make<void>(), notice: notice(messageID) }
State.set(input.child, entry)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Pause-registry entry can leak when the acquire phase is interrupted

State.set(input.child, entry) registers the entry before the rest of the Effect.acquireUseRelease acquire phase, which is still interruptible: the stopped re-check calls KiloSessionControl.paused, publish calls sessions.touch, and entry.notice calls input.jobs.get plus the board post. Effect does not run release when acquire is interrupted, so an interrupt landing in that window (a tree/job cancel or instance dispose while the turn is entering its pause) skips State.remove. The module-global pauses map then keeps the entry for the life of the process: project keeps reporting kilo.task: { status: "paused" } for a child that is no longer paused, and extend keeps taking the direct-prompt path for that child ID. Consider doing the fallible work before State.set, registering the entry in the use phase, or masking the registration so removal is guaranteed even on interruption.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

* an interrupt (`session`) would pause the parent's task, and this view has no prompt to resume it.
*/
export function cancelScope(parentID: string | null | undefined): "session" | "tree" {
return parentID ? "tree" : "session"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Cancel in a subagent view can pause the parent when the session is not hydrated yet

cancelScope maps an unknown parentID to a root session ("session"), but the abort is coming from a subagent's own view. currentSession() is store.sessions[currentSessionID()] (session.tsx:2740), and the subagent viewer only calls setCurrentSessionID on open (App.tsx:369); the record carrying parentID arrives later through the async registerSession (SubAgentViewerProvider.ts:64-71). If Cancel is pressed in that window, the abort is sent with scope: "session", which pauses the parent's task instead of cancelling the subagent — the behavior this change is meant to avoid, and this view has no prompt to resume it. Prefer deriving the scope from data already available at abort time (for example the tracked session list/metadata) instead of defaulting an unknown parent to root.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

…sk_id prompts

- The pause registers as the only acquire step; the board notice and the
  marker publish run in the use step, so a failing notice cannot leave the
  child shown as paused.
- A task_id sent to a paused task returns once the child admits the
  prompt, and fails with the cause when the run fails before that.
- Wait for the board notice in tests instead of reading it right after
  the pause registers, and match the footer's narrow threshold.
@calebnorman

Copy link
Copy Markdown
Contributor Author

All four issues are addressed in 4d38eb7.

  • Pause entry leak (task-pause.ts): the acquire step now only creates the entry and registers it, and neither of those can fail. The stop re-check, the board notice, and the marker publish all run in use, so the release always unregisters. The notice is also caught on its own path, because onPromote runs it through announce.
  • extend reporting success for a failed delivery: extend now registers a watcher on the child. The prompt's admission hook (KiloTaskPauseState.admit) completes the watcher and resumes the paused run. The task tool returns "Background task updated" only once the child has admitted the prompt. If the direct run fails before that, the task_id call fails with the cause. New test: "a task_id whose prompt fails before admission reports the failure".
  • Board-notice races in tests: the two positive assertions (the background interrupt test and the re-pause test) now wait for the expected number of pause notices instead of reading the board right after the pause registers. The two "no post" assertions for foreground tasks are unchanged. A foreground notice returns before posting, so those reads can't fail spuriously.
  • TUI width threshold: the assertion now uses width - 4 >= 96, which matches the footer's narrow rule.

Checks:

  • packages/opencode: typecheck passes; task-pause 17 pass on two runs; prompt, task, steering, scoped-abort, goal, and board suites 238 pass.
  • packages/tui: typecheck passes; test/kilocode 79 pass.
  • oxlint on the touched source files: 0 warnings.
  • The annotation check passes.

@github-actions github-actions Bot added area:cli CLI, TUI and core engine area:sdk SDK area:vscode VS Code extension community Opened by someone outside the Kilo team needs-review labels Oct 8, 2026
@github-actions github-actions Bot added needs-triage Waiting for a maintainer to look priority:p2 Medium: features and other work. Set from the title. Change by hand. labels Oct 16, 2026

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

area:cli CLI, TUI and core engine area:sdk SDK area:vscode VS Code extension community Opened by someone outside the Kilo team needs-triage Waiting for a maintainer to look priority:p2 Medium: features and other work. Set from the title. Change by hand.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants