Skip to content

fix(cli): wait for background continuations before headless exit - #13623

Merged
marius-kilocode merged 7 commits into
mainfrom
fix/headless-session-drain
Sep 1, 2026
Merged

fix(cli): wait for background continuations before headless exit#13623
marius-kilocode merged 7 commits into
mainfrom
fix/headless-session-drain

Conversation

@lambertjosh

@lambertjosh lambertjosh commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Problem

Headless kilo run can return exit code 0 while a background subagent or the parent's continuation still has unfinished work. This affects autonomous --auto runs, but the completion bug is not specific to that permission mode.

There are two observed orderings:

  1. Child finishes while the parent is busy. The child result is queued as a synthetic parent prompt. The current parent turn yields to that prompt, publishes an intermediate idle, and the CLI exits before the queued continuation runs.
  2. Parent idles before the child finishes. The CLI treats that first idle as final completion and tears down before the child can deliver its result.

finish_reason: tool-calls is normal for an intermediate model step; it is not itself an error. It becomes suspicious here because it is the last recorded step and required work is missing. The second ordering can even end with finish_reason: stop while the child remains active, so checking or rewriting the finish reason is not a fix.

Why this happens in the source

The old headless event consumer exits on the first parent idle:

if (
  event.type === "session.status" &&
  event.properties.sessionID === sessionID &&
  event.properties.status.type === "idle"
) {
  break
}
  • Old CLI idle exit, pinned to 7.5.6.
  • session/prompt.ts deliberately yields when a queued follow-up supersedes the current turn.
  • effect/runner.ts publishes idle before the current execution/queue handoff has fully settled.
  • The old Task notification path forks parent injection and ignores its result. Background-job completion therefore does not mean parent-result processing is complete.
  • cli/effect-cmd.ts disposes the local instance when the command handler returns.

Interactive clients stay alive across idle periods, which is why this can be easy to miss when testing the same model interactively.

Concrete evidence

Isolated before/after matrix

We used the real Linux CLI and a local scripted OpenAI-compatible provider. The task must obtain a child result, have the parent consume it, write a final marker, and emit the final answer. The verifier does not treat exit code 0 as success.

The three execution paths were:

  • Bare CLI: kilo run --auto --format json, without Harbor or the redactor.
  • Adapter wrapper: the actual generated auth/redactor/tee command, without Harbor orchestration.
  • Harbor: the real adapter and verifier in Docker.

PASS means the parent processed the child's result, wrote the required final output, and emitted its final answer. FAIL means that output was missing. These labels do not refer to the process exit code.

Each execution path runs three test cases:

  • Foreground: the parent waits for the child inline.
  • Background / busy parent: the child finishes while the parent is still running another tool.
  • Background / idle parent: the parent ends its current turn before the child finishes.
Execution path Test case Before: Kilo 7.5.6 After: patched CLI
Bare CLI Foreground PASS PASS
Bare CLI Background / busy parent FAIL PASS
Bare CLI Background / idle parent FAIL PASS
Adapter wrapper Foreground PASS PASS
Adapter wrapper Background / busy parent FAIL PASS
Adapter wrapper Background / idle parent FAIL PASS
Harbor Foreground PASS PASS
Harbor Background / busy parent FAIL PASS
Harbor Background / idle parent FAIL PASS

Before: 3 passed, 6 failed. After: all 9 passed. The six failing cases still returned CLI exit code 0—that false appearance of completion is the bug. Harbor's verifier assigned reward 0 when the required output was missing and reward 1 when the probe passed.

All nine valid patched cases passed. A fresh unpatched 7.5.6 bare-CLI control still reproduced the incomplete successful exit. This establishes that Harbor and redaction are not required to cause the failure.

Discarded setup attempts are not counted in that matrix: an initial Harbor fixture had an ARM/AMD64 sidecar mismatch, and an early patched wrapper fixture selected the old executable through /usr/local/bin. Both were corrected and binary selection was checked before recording the valid comparisons.

Real-model confirmation, not just a scripted provider

A short real-model diagnostic made the child generate a random nonce. The parent was instructed not to read the nonce file; it had to obtain the value from the completed-task result, then write and report the matching value.

Before the fix:

  • The child finished its answer.
  • 44 ms later, its synthetic callback was persisted in the parent.
  • 8.402 seconds after child completion, the parent's held tool finished.
  • 8.448 seconds after child completion, the instance was disposed without another parent request.
  • The CLI returned 0 in approximately 34 seconds, well before the 120-second timeout. The parent marker was absent.

Patched build:

  • One background child was actually used.
  • The parent resumed after its held tool, wrote the matching random value, emitted PARENT_FINAL=<matching child nonce>, and finished with stop.
  • Exit code 0, no timeout, approximately 44 seconds.
  • Parent tool sequence: task(background=true) → readiness-only bashwrite(parent-final.txt) → final answer. The parent did not read the child nonce file.

The compiled probe build was 0.0.0-headless-drain-6137d1ea8d25, based on bbf6a278d7. This PR was subsequently rebased onto current main, preserving newer Task model-selection behavior; focused checks were rerun after rebasing.

These are completion probes, not benchmark scores. No full benchmark was rerun with the fix, and this PR does not claim that every previous benchmark failure had this cause.

Fix and architecture

Keep SessionStatus.idle meaning “the current turn is idle.” Add a small backend session-drain contract for headless completion, instead of changing interactive idle semantics or introducing a new run/cancel protocol.

flowchart LR
  A[Prompt queue and runner work] --> C[SessionDrain accounting]
  B[Task and parent-result delivery] --> C
  C --> D[No tracked work remains]
  D --> E[Publish session.drained token]
  E --> F[Return drain HTTP response]
  E --> G[CLI consumes acknowledgment]
  F --> H[CLI requires both]
  G --> H
  H --> I[Flush output and exit]
Loading

Backend accounting

src/kilocode/session/drain.ts uses the existing Effect/instance-state conventions:

  • Count active execution and the full prompt-queue reservation lifetime, including the handoff to queued work.
  • Link live child activity to its parent.
  • Reserve Task delivery before work can escape; retain it through child completion, callback injection, and parent processing.
  • Release reservations once, clean up interrupted waiters, and do not turn instance disposal into a successful drain.
  • Preserve foreground completion, promotion/extension handling, and recoverable child errors/cancellation.

The accounting helper does not execute prompts or create another scheduler. It adds no database tables, durable run registry, new status enum, or per-invocation ownership framework. The core background-job completion contract remains unchanged.

Task result handoff

The Task notifier now awaits parent injection instead of detaching and ignoring it. Delivery errors are surfaced through the existing session error path.

A targeted regression also demonstrated an intermediate nested-child response being returned as final. Task now waits for the child's own drain, then uses a bounded latest-assistant lookup only when that assistant is newer than the initial response. This keeps a child's initial “waiting” response from being reported as its completed result.

Wait endpoint and output fence

  • POST /kilocode/session/:sessionID/drain accepts a bounded correlation token and waits for session drain.
  • The server publishes a matching non-durable session.drained event through the awaited event bridge before returning the HTTP response.
  • The CLI subscribes before submitting, continues consuming output/permissions/errors, and requires both the response and matching event. Either may arrive first at the client.
  • Final stdout/stderr writes are flushed before exit.

This avoids replacing one race with another where HTTP completion arrives before the consumer has processed buffered final output.

Headless compatibility and cleanup

  • Applies to all headless modes, including explicit attach and automatic daemon attachment; the old attach completion bypass is removed.
  • Existing prompt, command, and builtin submission APIs remain in place.
  • The CLI checks the API description before submission and rejects unsupported/HTML-returning older servers explicitly. Older daemons or attached servers must be upgraded/restarted; there is no silent fallback to first-idle completion.
  • EOF/disconnect before acknowledgment is failed or unknown completion, not success. There is no automatic resubmission/replay.
  • Awaited permission/network requests and retry waits receive the abort signal so cleanup cannot hang on a stalled reply.
  • Permission/network handling remains bounded to the root and tracked Task descendants; unrelated sessions are ignored.
  • This is session-level draining: concurrent work added to the same session may extend the wait. Inactive historical children and persistent background_process servers/watchers do not block it.

Reviewer guide

Area Main files
Drain invariant and waiter lifecycle packages/opencode/src/kilocode/session/drain.ts
CLI readiness, capability check, acknowledgment, cancellation and output flush packages/opencode/src/kilocode/cli/run-drain.ts, packages/opencode/src/cli/cmd/run.ts
Queue/runner reservation boundaries packages/opencode/src/session/prompt.ts, packages/opencode/src/session/run-state.ts
Task admission, notification supervision and final child result packages/opencode/src/tool/task.ts
Wait API and event schema packages/opencode/src/kilocode/server/httpapi/{groups,handlers}/kilocode.ts, packages/schema/src/kilocode/session-drain.ts
Deterministic end-to-end regressions packages/opencode/test/kilocode/headless-session-drain.test.ts
Accounting, transport, permissions and nested-result coverage session-drain.test.ts, run-drain.test.ts, run-auto.test.ts, test/tool/task.test.ts

The remaining changes are dependency-layer wiring, generated SDK/OpenAPI files, test-layer wiring, and a CLI/SDK patch changeset.

Validation

  • Permanent foreground, busy-parent, idle-parent, attached-mode, SDK-acknowledgment, cancellation, nested-result, scoped-permission and output/error regressions.
  • Permanent handoff tests use actual queue events and Deferred/Latch gates rather than sleeps. The exploratory container probe used controlled HTTP barriers with a short settling interval; the saved real-model timeline independently showed the callback persisted well before exit.
  • Pre-rebase validation: 152 targeted tests passed across the CLI, Task, API and SDK suites; full workspace typechecking passed; lint completed with warnings and no errors; SDK generation/build and required annotation/promise-facade guards passed.
  • Post-rebase validation at 7c845aa0ec: 110 focused tests passed across six files, including both headless orderings, explicit attach, drain/SDK behavior, Task lifecycle, nested results, and the newly merged Task model-selection cases. Full workspace typechecking passed (30 tasks); API regeneration left the tree unchanged; annotation, promise-facade, and the new duplication guard passed.
  • Compiled validation used unpublished Linux/x64-baseline binaries under Docker on Apple Silicon. The test build omitted bundled bubblewrap; these probes are not a sandbox-validation claim.
  • No deployment, full-suite benchmark run, or public artifact upload was performed.

From packages/opencode, the focused post-rebase command was:

env -u KILO_TEST_CLI_PATH bun test \
  ./test/kilocode/headless-session-drain.test.ts \
  ./test/kilocode/session-drain.test.ts \
  ./test/kilocode/run-drain.test.ts \
  ./test/tool/task.test.ts \
  ./test/kilocode/task-nesting.test.ts \
  ./test/kilocode/tool-task-model.test.ts \
  --max-concurrency 3

From the repository root:

./script/generate.ts
bun run typecheck
bun run script/check-opencode-annotations.ts --base origin/main
bun run script/check-opencode-promise-facades.ts
bun run check:duplication

Privacy and evidence limits

This description contains only sanitized timelines, aggregate outcomes, test commands, public source references and implementation details. It includes no API keys, auth headers, tenant IDs, raw CLI databases, or private artifact archives. Authentication-bearing temporary CLI state was removed, and retained probe output plus the proposed source changes were scanned for known credential values.

The diagnostic cost collector only measured streamed parent-step cost, so no total-cost claim is made here. Separate adapter provisioning/HOME issues and gateway payload-size failures are outside this fix.

Comment thread packages/opencode/src/tool/task.ts Outdated
})

function backgroundResult() {
delivery.retained = true // kilocode_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.

[WARNING]: Promotion can retain the drain hold without installing a releaser

backgroundResult() sets delivery.retained = true before notify() has forked the ensuring(delivery.release) fiber. BackgroundJob.promote resolves waitForPromotion before running onPromote, and onPromote is Effect.ignore. If this path returns at the promotion check and ctx.metadata / notify then fails, execute's release sees retained and skips release(), so drain.wait never completes and headless kilo run hangs.

Consider setting retained only after notify() has actually forked, and invoking notify() on the promotion path only if it has not already run.


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

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.

Fixed in a3f4b46. The background-result formatter no longer transfers ownership. The Task execution installs notification cleanup before returning a promoted result; the promotion hook now only updates metadata. A child scope also guarantees release if the notification is interrupted before its worker starts. Added a deterministic regression with a blocked, then failing metadata hook that verifies exactly one delivery and no early drain.

for (let current: Entry | undefined = value; current; current = current.parent) {
current.count += delta
if (current.count !== 0) continue
for (const waiter of current.waiters) Deferred.doneUnsafe(waiter, Effect.void)

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]: Instance disposal can complete drain waiters as success

update finishes waiters with Effect.void whenever count hits 0. disposeInstance runs InstanceState invalidations concurrently (Promise.allSettled). If SessionRunState cancels runners first, drain.track releases while closed is still false, waiters succeed, and drainSession can publish session.drained / HTTP true for cancelled work. After re-init, a later wait also sees count === 0 and succeeds.

The stated invariant is that disposal is not a successful drain. Consider completing in-flight waiters with interrupt (and failing post-dispose wait) before other caches cancel work, or treating closed as failure even when count is already 0.


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

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.

Fixed in a3f4b46. A synchronous pre-disposal phase interrupts drain waiters before any asynchronous cache cancellation runs. Drain state is now owned by its scoped service and tracks instance identity rather than allowing an invalidated cache to recreate successful empty state. Tests release the last hold during a gated disposer, verify interruption during and after disposal, verify a fresh instance can start, and verify another directory remains unaffected. The architecture check passes without a new allowlist exemption.

[process.stdout, process.stderr].map(
(stream) =>
new Promise<void>((resolve, reject) => {
stream.write("", (error) => (error ? reject(error) : resolve()))

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]: Output flush turns EPIPE into a failed run

flush() rejects on any stdout/stderr write error. run.ts awaits this in finally after a successful drain, outside the catch that sets exitCode. kilo run … | head or | jq (EPIPE / write-after-close) can fail a completed run.

Ignore EPIPE / ERR_STREAM_DESTROYED, or resolve rather than reject on write errors.


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

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.

Fixed in a3f4b46. The final flush tolerates EPIPE and ERR_STREAM_DESTROYED, for both callback errors and synchronous write failures. Other I/O errors still reject. Tests cover both closed-stream cases, preservation of EIO failures, and waiting for both output callbacks.

}
const request = new Request(`${config.baseUrl.replace(/\/$/, "")}/doc`, { headers, signal })
const response = await (config.fetch ?? globalThis.fetch)(request)
if (!response.ok || !response.headers.get("content-type")?.toLowerCase().includes("application/json")) {

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]: Capability check reports every non-OK /doc as an old server

A 401/403 on --attach, a 5xx, or a non-JSON error body all throw "Server does not support session draining; upgrade or restart the server". Auth and transport failures are misdiagnosed as a missing drain API.

Reserve that message for missing/HTML/unrelated OpenAPI; surface status/auth errors separately.


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

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.

Fixed in a3f4b46. HTTP 401/403 now report authentication/authorization rejection, and other non-404 failure statuses preserve their HTTP code instead of recommending an upgrade. Invalid capability JSON has a separate diagnostic without exposing the response body. Missing, HTML, and unrelated OpenAPI responses retain the unsupported-server diagnostic. Added coverage for 401, 403, 429, 500, 503, and malformed JSON.

Effect.gen(function* () {
const state = yield* InstanceState.make(() =>
Effect.gen(function* () {
const entries = new Map<SessionID, 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.

[SUGGESTION]: Drain entries are never pruned until instance dispose

entry() always inserts, and update never deletes when count returns to 0. A long-lived kilo serve / VS Code backend retains one Entry per session that was held, linked, or drained.

Consider dropping idle nodes with no waiters and no children so the map cannot grow without bound.


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

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.

Improved in a3f4b46: idle wait queries no longer create entries, and unlinked idle entries are pruned when no holds, children, or waiters remain. Waiters pin their entry until they finish, with a controlled-scheduler regression covering release/reacquire before an awakened waiter resumes. Linked ancestry is deliberately retained until instance disposal so resuming an idle child still contributes to its original parent; that behavior now has a regression test too. This is not a claim of a strict bound on historical linked topology.

@kilo-code-bot

kilo-code-bot Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (26 files)
  • .changeset/headless-session-drain.md
  • packages/opencode/src/cli/cmd/run.ts
  • packages/opencode/src/effect/instance-registry.ts
  • packages/opencode/src/effect/runner.ts
  • packages/opencode/src/kilocode/cli/cmd/run.ts
  • packages/opencode/src/kilocode/cli/run-drain.ts
  • packages/opencode/src/kilocode/effect/instance-registry.ts
  • packages/opencode/src/kilocode/effect/runner.ts
  • packages/opencode/src/kilocode/session/drain.ts
  • packages/opencode/src/kilocode/session/prompt.ts
  • packages/opencode/src/session/prompt.ts
  • packages/opencode/src/session/run-state.ts
  • packages/opencode/src/tool/task.ts
  • packages/opencode/test/event-manifest.test.ts
  • packages/opencode/test/kilocode/headless-session-drain.test.ts
  • packages/opencode/test/kilocode/run-auto.test.ts
  • packages/opencode/test/kilocode/run-drain.test.ts
  • packages/opencode/test/kilocode/run-network.test.ts
  • packages/opencode/test/kilocode/runner-start-order.test.ts
  • packages/opencode/test/kilocode/session-drain.test.ts
  • packages/opencode/test/kilocode/task-nesting.test.ts
  • packages/opencode/test/tool/task.test.ts
  • packages/schema/src/kilocode/session-drain.ts
  • packages/sdk/js/src/v2/gen/sdk.gen.ts
  • packages/sdk/js/src/v2/gen/types.gen.ts
  • packages/sdk/openapi.json
Previous Review Summaries (3 snapshots, latest commit ac54cca)

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

Previous review (commit ac54cca)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (10 files)
  • .changeset/headless-session-drain.md
  • packages/opencode/src/cli/cmd/run.ts
  • packages/opencode/src/kilocode/cli/run-drain.ts
  • packages/opencode/src/kilocode/session/drain.ts
  • packages/opencode/src/server/shared/workspace-routing.ts
  • packages/opencode/src/tool/task.ts
  • packages/opencode/test/kilocode/headless-session-drain.test.ts
  • packages/opencode/test/kilocode/run-drain.test.ts
  • packages/opencode/test/kilocode/session-drain.test.ts
  • packages/opencode/test/tool/task.test.ts

Previous review (commit 383d9e9)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (12 files)
  • packages/opencode/src/effect/instance-registry.ts
  • packages/opencode/src/kilocode/cli/run-drain.ts
  • packages/opencode/src/kilocode/session/drain.ts
  • packages/opencode/src/tool/task.ts
  • packages/opencode/test/event-manifest.test.ts
  • packages/opencode/test/kilocode/headless-session-drain.test.ts
  • packages/opencode/test/kilocode/run-auto.test.ts
  • packages/opencode/test/kilocode/run-drain.test.ts
  • packages/opencode/test/kilocode/run-network.test.ts
  • packages/opencode/test/kilocode/server/httpapi-exercise-scenarios.ts
  • packages/opencode/test/kilocode/session-drain.test.ts
  • packages/opencode/test/tool/task.test.ts

Previous review (commit 7c845aa)

Status: 5 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 4
SUGGESTION 1

Fix these issues in Kilo Cloud

Issue Details (click to expand)

WARNING

File Line Issue
packages/opencode/src/tool/task.ts 427 Promotion can retain the drain hold without installing a releaser
packages/opencode/src/kilocode/session/drain.ts 56 Instance disposal can complete drain waiters as success
packages/opencode/src/kilocode/cli/run-drain.ts 104 Output flush turns EPIPE into a failed run
packages/opencode/src/kilocode/cli/run-drain.ts 34 Capability check reports every non-OK /doc as an old server

SUGGESTION

File Line Issue
packages/opencode/src/kilocode/session/drain.ts 28 Drain entries are never pruned until instance dispose
Files Reviewed (27 files)
  • .changeset/headless-session-drain.md
  • packages/opencode/src/cli/cmd/run.ts
  • packages/opencode/src/effect/app-runtime.ts
  • packages/opencode/src/kilocode/cli/cmd/run.ts
  • packages/opencode/src/kilocode/cli/run-auto.ts
  • packages/opencode/src/kilocode/cli/run-drain.ts - 2 issues
  • packages/opencode/src/kilocode/server/httpapi/groups/kilocode.ts
  • packages/opencode/src/kilocode/server/httpapi/handlers/kilocode.ts
  • packages/opencode/src/kilocode/session/drain.ts - 2 issues
  • packages/opencode/src/server/routes/instance/httpapi/server.ts
  • packages/opencode/src/session/prompt.ts
  • packages/opencode/src/session/run-state.ts
  • packages/opencode/src/tool/registry.ts
  • packages/opencode/src/tool/task.ts - 1 issue
  • packages/opencode/test/kilocode/cli-run-auto-helper.test.ts
  • packages/opencode/test/kilocode/headless-session-drain.test.ts
  • packages/opencode/test/kilocode/run-auto.test.ts
  • packages/opencode/test/kilocode/run-drain.test.ts
  • packages/opencode/test/kilocode/session-drain.test.ts
  • packages/opencode/test/kilocode/task-nesting.test.ts
  • packages/opencode/test/kilocode/tool-task-model.test.ts
  • packages/opencode/test/tool/task.test.ts
  • packages/schema/src/event-manifest.ts
  • packages/schema/src/kilocode/session-drain.ts
  • packages/sdk/js/src/v2/gen/sdk.gen.ts
  • packages/sdk/js/src/v2/gen/types.gen.ts
  • packages/sdk/openapi.json

Reviewed by grok-4.6 · Input: 191.3K · Output: 37.4K · Cached: 2.2M

Review guidance: REVIEW.md from base branch main

@lambertjosh

Copy link
Copy Markdown
Contributor Author

Review/CI follow-up pushed in a3f4b46 and 383d9e9. Replied to all five review comments.

  • Fixed promotion ownership, disposal-before-cancellation ordering, closed-output flushing, and capability-error diagnostics.
  • Avoided idle-query allocations and safely pruned unlinked entries; linked ancestry remains intentionally retained for resumed children, as explained in the review thread.
  • Added a synchronous re-entry regression and snapshot waiter lists before waking them.
  • Updated the event inventory, HTTP exerciser scenarios, network/capability/SSE mocks, retry-clock mocking, and explicit test exit-code restoration.
  • Replaced the Windows-sensitive inline JavaScript command with a file-based fixture. The Windows logs exposed only interruption, so this addresses the leading shell-escaping explanation; Windows CI still needs to confirm it.

Local validation: 77 targeted tests pass, the full HTTP exerciser passes, root typechecks pass, lint reports no errors, and annotation/architecture/promise-facade/duplication checks pass without new exemptions. Fresh CI is running; no claim yet that Windows is green.

lambertjosh and others added 4 commits August 31, 2026 23:55
Route drain requests and CLI output through the session owner directory. Simplify notification holds and completion waiters while preserving disposal safety. Cover local, attached, and daemon cross-directory runs.
Keep reservations through prompt intake, runner completion, and shell cleanup. Report cancellation after the parent turn has ended without emitting another turn close.

Keep disposal bookkeeping in Kilo-owned code and consolidate regression fixtures without dropping the required coverage.
Preserve scoped cancellation and drain ownership through Task cleanup. Register promoted-job delivery independently of the parent caller while preventing duplicate notifications.

Keep current-only parent interruption distinct from tree cancellation and verify the combined behavior with the real CLI regression suite.
@marius-kilocode
marius-kilocode merged commit c2030d3 into main Sep 1, 2026
33 checks passed
@marius-kilocode
marius-kilocode deleted the fix/headless-session-drain branch September 1, 2026 12:51
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.

2 participants