Skip to content

fix(cli): flush the session ingest tail on shutdown - #12545

Merged
iscekic merged 9 commits into
mainfrom
fix/cli-ingest-flush-on-shutdown
Jul 27, 2026
Merged

fix(cli): flush the session ingest tail on shutdown#12545
iscekic merged 9 commits into
mainfrom
fix/cli-ingest-flush-on-shutdown

Conversation

@iscekic

@iscekic iscekic commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator

Problem

While a Kilo CLI session is live, the mobile session-detail screen streams assistant messages in over the relay. Opening the same session later read-only, the transcript is often missing the last message(s) the user watched arrive.

Root cause (CLI-side): the durable transcript depends on a client-side coalescing queue (IngestQueue) that flushes at most once per ~1 s per session and was never flushed on any exit path — anything still queued when the process ends is lost permanently, even though the cloud already received (and discarded after broadcast) every one of those events over the live relay. Confirmed reproduction rates on the unmodified baseline: clean /exit immediate 3/3, SIGINT 3/3, SIGHUP 1/1 lost the tail; delayed /exit (≥10 s) 0/2.

Fix

  • Shutdown drain in IngestQueue. A new drain() flushes every pending session concurrently: bounded at 3 s (the TUI parent terminates the worker unconditionally 5 s after asking it to shut down), joins in-flight flushes rather than just the queue map (an empty map is not quiescence), suppresses re-enqueue globally once shutdown begins (a joined flush that fails retryably cannot re-queue the tail behind the drain's back), and falls back to the last-known client/share without re-validating auth so the tail is actually POSTed at teardown instead of silently dropped on getClient()'s HTTP authValid check.
  • Wired into the real process shutdown paths (K2), always before disposeAllInstances() so the GlobalBus handler and remote wiring stay live during the drain:
    • embedded TUI worker shutdown RPC — covers the three reproduced endings (/exit, SIGHUP, SIGTERM), via a pure createWorkerShutdown({ drain, dispose, stopServer }) seam;
    • KiloShutdown registry for non-TUI commands (kilo run, …) reaching the top-level finally;
    • the kilo serve signal handler, so the daemon child drains when the daemon itself is stopped.
    • A module-level once-guard prevents overlapping paths from double-POSTing. The per-directory instance finalizer is deliberately not hooked (wrong granularity for a module-level singleton queue).
  • Terminal batches flush promptly (K3). A batch containing session_close no longer inherits the open ~1 s debounce window: sync() bases its deadline on max(now(), retry.until) and schedule() lets terminal batches move a flush earlier. Streaming part/message coalescing is unchanged. This narrows the loss window for endings that cannot be intercepted (kill -9, crash, power loss) from ~1 s to a single POST round-trip — it does not close it.
  • SIGINT parity (K4). An external kill -INT now takes the same graceful path as SIGHUP/SIGTERM. Interactive Ctrl-C is a raw-mode keypress, not a signal, so interactive behaviour is unchanged.

Tests

23 unit tests in packages/opencode/test/kilocode/sessions/: drain flushes all pending sessions (queue pinned emptied on success, not re-enqueued on failure), drain POSTs via the cached client/share when fresh resolution fails at teardown, drain joins a mid-POST flush and suppresses its re-enqueue on retryable failure, terminal scheduling (close-into-open-window pulls the flush to ~0, non-terminal stays at now()+1000, retry backoff still respected), worker-seam ordering (drain before dispose), once-guard. The 12 pre-existing ingest-queue tests are unmodified and green. bun run typecheck and oxlint (0 errors) pass.

Deliberate two-PR deviation

The confirmed root cause is in the CLI, so the fix ships from this repo. The companion cloud PR Kilo-Org/cloud#4786 carries only the E2E harness change that makes on-device verification of this CLI-side fix possible at all. The batch instruction said one PR from the cloud worktree; the deviation is deliberate and stated in both PR bodies.

Residuals (documented non-goals)

  • kill -9, crashes and mid-turn kills keep losing an unflushed tail; the terminal-flush change narrows but cannot close that window.
  • A daemon-attached client exit was never reproduced and is not covered by the on-device E2E: the daemon outlives the TUI client, so its ~1 s timer flush still lands — argued unexposed, not measured.
  • The kilo serve drain is unit-wired with an inspected call site but no daemon-stop E2E (no reproduction exists for that path).

Verification

  • Unit: drain, in-flight join, re-enqueue suppression, cached-client delivery, terminal scheduling, seam ordering, once-guard
  • On-device E2E per ending (/exit, SIGHUP, SIGINT; delayed-/exit control) — ACCEPT 11/11, posted as a (bot) comment with the running CLI's version stamp

const share = await options.getShare(sessionId).catch(() => undefined)
if (!share) return
const freshShare = await options.getShare(sessionId).catch(() => undefined)
if (freshShare) shares.set(sessionId, freshShare)

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: shares map grows unbounded for the lifetime of the process

shares.set(sessionId, freshShare) runs on every successful getShare resolution and there is no corresponding delete anywhere in this file. In a long-running kilo serve daemon (or a TUI worker that lives across many sessions), every session that has ever synced a share accumulates a permanent entry here (and cached similarly never expires, though that's a single value so it's less of a concern). Since this cache exists purely as a shutdown-drain fallback, consider bounding it — e.g. drop the entry for a session once its session_close batch is successfully flushed, or cap shares to a small LRU — so it doesn't grow proportionally to total sessions served over the process lifetime.


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

shutting = true
const deadline = now() + bound

for (const sessionId of Array.from(queue.keys())) {

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: Redundant initial flush loop before while

This for loop that kicks off flush() for every queued session is functionally identical to the for loop at the top of the while body below (lines 397-399). Since the while condition itself checks queue.size > 0, dropping this initial loop and letting the while loop's own first iteration do the same work would remove the duplication without changing behavior.


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

@kilo-code-bot

kilo-code-bot Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No New Issues Found | Recommendation: Merge

Overview

This increment (vs. previously reviewed commit 0c3cc0f5a9) touches only packages/opencode/test/kilocode/cli-shutdown.test.ts: it adds an installDrain() helper (calls KiloShutdown.run() to flush/clear any leftover registered task, resets the shared calls/drainCalls counters, then registers a fresh drain task mirroring the mocked kilo-sessions module) and calls it from the two later tests ("keeps telemetry shutdown timeout best-effort..." and "preserves failing command exit status"). Those two tests' expect(calls) assertions now include "drain" between "telemetry" and "dispose". The comment above the first test is updated to explain that only that test relies on setup.ts's one-time module-scope KiloShutdown.register() call, while the later tests self-install their own task via the new helper so they no longer depend on suite declaration order or on whether an earlier test already drained the registry via KiloShutdown.run(). This matches the real registration/consumption semantics in src/kilocode/cli/shutdown.ts (register/run on a Set, cleared on each run()) and setup.ts (drain registered before dispose). No product code changed in this increment; no bugs, style, or fork-hygiene issues found in the changed lines.

The previously flagged unbounded shares cache in ingest-queue.ts remains outdated per current GitHub comment state (line: null) and stays dropped. The redundant initial flush loop in drain() (ingest-queue.ts:400) remains open as a standing suggestion but is outside this increment's changed files (test-only diff).

Files Reviewed (1 file)
  • packages/opencode/test/kilocode/cli-shutdown.test.ts
Previous Review Summaries (7 snapshots, latest commit 0c3cc0f)

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

Previous review (commit 0c3cc0f)

Status: No New Issues Found | Recommendation: Merge

Overview

This increment (vs. previously reviewed commit ea6b7e5b802d3b21602a92c227a3603506b12789) adds a single new unit test to ingest-queue.test.ts: drain does not re-enqueue on retryable HTTP status under shutdown. It exercises IngestQueue.drain() against a mocked 429 response and asserts that the shutdown path logs { status: 429, shutdown: true } and drops the item without re-enqueuing (sched.size() returns to 0). This matches the real run() implementation in ingest-queue.ts (lines 339-346), which under shutting logs the retryable status with shutdown: true and returns without calling requeue/retry.set. The test follows the existing file's conventions (as any casts on synthetic session data, shared scheduler/clock helpers) and exercises the actual implementation rather than duplicating its logic. No bugs, style, or fork-hygiene issues in the changed lines.

The previously flagged unbounded shares cache in ingest-queue.ts is now outdated per current GitHub comment state (line: null) and remains dropped. The redundant initial flush loop in drain() (ingest-queue.ts:400) is still open as a standing suggestion but is outside this increment's changed files (test-only diff).

Files Reviewed (1 file)
  • packages/opencode/test/kilocode/sessions/ingest-queue.test.ts

Previous review (commit ea6b7e5)

Status: No New Issues Found | Recommendation: Merge

Overview

This increment (vs. previously reviewed commit b2540c490969245adcf57ef068d179c18ebf1e33) touches ingest-drain.ts, kilo-sessions.ts, setup.ts, and a new test in ingest-drain.test.ts. IngestDrain.create now takes an optional onError callback and its guarded promise catches the underlying run() rejection internally instead of propagating it, so overlapping/late callers always resolve and the drain failure is logged once rather than risking an unguarded reject in serve.ts / worker.ts, which await drainIngestForShutdown() directly before dispose/stop. kilo-sessions.ts wires the new onError to log.warn. setup.ts's comment is corrected to describe the dynamic-import rationale as being about avoiding module-mock linking failures in tests, not runtime import-graph cost, matching the code's try/catch scope (dynamic-import failure only, since the drain itself no longer rejects). The new test verifies the guard resolves, logs exactly once, and does not retry on a rejecting run(). No bugs, style, or fork-hygiene issues found in the changed lines.

The previously flagged unbounded shares cache in ingest-queue.ts is now outdated per current GitHub comment state (line: null) and is dropped from this summary. The redundant initial flush loop in drain() (ingest-queue.ts:400) remains open as a standing suggestion but is outside this increment's changed files.

Files Reviewed (4 files)
  • packages/opencode/src/kilo-sessions/ingest-drain.ts
  • packages/opencode/src/kilo-sessions/kilo-sessions.ts
  • packages/opencode/src/kilocode/cli/setup.ts
  • packages/opencode/test/kilocode/sessions/ingest-drain.test.ts

Previous review (commit b2540c4)

Status: No New Issues Found | Recommendation: Merge

Overview

This increment (vs. previously reviewed commit bc34014e91aa1be13ea68b2b72b1d574ad0b0059) touches only packages/opencode/test/kilocode/cli-shutdown.test.ts: it pushes "drain" into the shared calls array inside the mocked KiloSessions.drainIngestForShutdown(), updates the expect(calls) assertion in the "rejects drain without blocking dispose" test to include it, and adds a clarifying comment. This makes the existing test actually assert drain-runs-before-dispose ordering rather than merely counting calls implicitly. No product code changed in this increment; no new bugs, style, or fork-hygiene issues found.

The previously flagged unbounded shares cache in ingest-queue.ts is now outdated per current GitHub comment state (line: null) and is dropped from this summary. The redundant initial flush loop in drain() (ingest-queue.ts:400) remains open as a standing suggestion but is outside this increment's changed files.

Files Reviewed (1 file)
  • packages/opencode/test/kilocode/cli-shutdown.test.ts

Previous review (commit bc34014)

Status: 1 Issue Found | Recommendation: Merge

Overview

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

SUGGESTION

File Line Issue
packages/opencode/src/kilo-sessions/ingest-queue.ts 400 Initial flush loop in drain() still duplicates the while loop's own first-iteration flush loop — carried forward, unchanged in this round

This round's diff (vs. previously reviewed commit 0865051a30e89e640c9dd67287a0684e5f090254) touches only packages/opencode/src/kilocode/cli/setup.ts and its test. It wraps the KiloShutdown.register() drain callback in try/catch, logging via log.warn instead of letting a rejection propagate. This is correct: KiloShutdown.run() uses Promise.all over registered tasks, so an unguarded rejection from the ingest drain would have propagated through KiloCli.shutdown() and skipped the subsequent disposeAllInstances() call. The new test ("rejects drain without blocking dispose") directly exercises this path and confirms dispose still runs and process.exitCode is preserved on drain failure. No new issues found.

The previously flagged unbounded shares cache in ingest-queue.ts remains outdated per current GitHub comment state (line resolved to null) and stays dropped from this summary. The redundant initial flush loop in drain() remains open, verified present and unchanged at current HEAD.

Fix these issues in Kilo Cloud

Files Reviewed (2 files)
  • packages/opencode/src/kilocode/cli/setup.ts
  • packages/opencode/test/kilocode/cli-shutdown.test.ts

Previous review (commit 0865051)

Status: 1 Issue Found | Recommendation: Merge

Overview

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

SUGGESTION

File Line Issue
packages/opencode/src/kilo-sessions/ingest-queue.ts 400 Initial flush loop in drain() still duplicates the while loop's own first-iteration flush loop — carried forward, unchanged in this round

This round's diff (vs. previously reviewed commit 621ebc89b8f06139cfaf1f215183ffd24d7110e8) is a single commit that converts the module-level import { KiloSessions } from "@/kilo-sessions/kilo-sessions" in setup.ts into a lazy await import(...) inside the KiloShutdown.register() callback, so fast paths like kilo --help no longer eagerly pull the provider/plugin graph into startup. cli-shutdown.test.ts gained a matching mock.module("@/kilo-sessions/kilo-sessions", ...) stub so the test doesn't load the real module. The change is mechanical, preserves the documented shutdown ordering (KiloShutdown.run() still runs before disposeAllInstances() in setup.ts's shutdown()), and introduces no new issues.

The previously flagged unbounded shares cache in ingest-queue.ts is now outdated per current GitHub comment state and has been dropped from this summary; the redundant initial flush loop in drain() remains open and is unaffected by this round's changes.

Fix these issues in Kilo Cloud

Files Reviewed (2 files)
  • packages/opencode/src/kilocode/cli/setup.ts
  • packages/opencode/test/kilocode/cli-shutdown.test.ts

Previous review (commit 621ebc8)

Status: 2 Issues Found | Recommendation: Address before merge

Overview

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

WARNING

File Line Issue
packages/opencode/src/kilo-sessions/ingest-queue.ts 236 shares cache map still grows unbounded for the process lifetime (no eviction on session close) — carried forward from prior round, now inside the extracted resolveShare() helper

SUGGESTION

File Line Issue
packages/opencode/src/kilo-sessions/ingest-queue.ts 400 Initial flush loop in drain() still duplicates the while loop's own first-iteration flush loop — unchanged in this round

This round's diff (vs. previously reviewed commit 6074e8ec5eaa6d604068c263cd1a35d162727edd) is a mechanical refactor plus a changeset: run()'s inline share/client resolution was extracted into resolveShare()/resolveClient() helpers (no behavior change — the shutdown-fallback and error-swallowing logic is preserved verbatim), and a new unit test exercises drain()'s 3s bound-expiry path against a never-settling flush (previously untested). A .changeset/ingest-shutdown-flush.md patch entry was also added, addressing the missing-changeset note from the prior review.

The two previously flagged issues (unbounded shares cache, redundant initial flush loop in drain()) are untouched by this round's changes and remain open at their new line numbers above. No new issues were introduced by the extraction or the added test.

Fix these issues in Kilo Cloud

Files Reviewed (3 files)
  • .changeset/ingest-shutdown-flush.md
  • packages/opencode/src/kilo-sessions/ingest-queue.ts - 2 issues (carried forward)
  • packages/opencode/test/kilocode/sessions/ingest-queue.test.ts

Previous review (commit 6074e8e)

Status: 2 Issues Found | Recommendation: Address before merge

Overview

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

WARNING

File Line Issue
packages/opencode/src/kilo-sessions/ingest-queue.ts 237 shares cache map grows unbounded for the process lifetime (no eviction on session close)

SUGGESTION

File Line Issue
packages/opencode/src/kilo-sessions/ingest-queue.ts 392 Initial flush loop in drain() duplicates the while loop's own first-iteration flush loop

The core drain() state machine (queue → inflight join, shutdown-mode no-re-enqueue, cached-client/share fallback) checked out under manual trace of the concurrency invariants, and the shutdown wiring order (drain → dispose → stopServer, drain before disposeAllInstances) matches the PR description across serve.ts, worker.ts/worker-shutdown.ts, and setup.ts. The IngestDrain once-guard correctly de-dupes overlapping shutdown paths. kilocode_change annotations on shared files and the kilo-sessions/kilo-* exemptions look correctly applied.

Note: this is a user-facing bug fix (lost session tail on exit) with no .changeset/*.md in the diff — worth adding one before merge per the repo's changeset policy.

Fix these issues in Kilo Cloud

Files Reviewed (11 files)
  • packages/opencode/src/cli/cmd/serve.ts
  • packages/opencode/src/cli/cmd/tui.ts
  • packages/opencode/src/cli/tui/worker-shutdown.ts
  • packages/opencode/src/cli/tui/worker.ts
  • packages/opencode/src/kilo-sessions/ingest-drain.ts
  • packages/opencode/src/kilo-sessions/ingest-queue.ts - 2 issues
  • packages/opencode/src/kilo-sessions/kilo-sessions.ts
  • packages/opencode/src/kilocode/cli/setup.ts
  • packages/opencode/test/kilocode/sessions/ingest-drain.test.ts
  • packages/opencode/test/kilocode/sessions/ingest-queue.test.ts
  • packages/opencode/test/kilocode/sessions/worker-shutdown.test.ts

Reviewed by claude-sonnet-5 · Input: 26 · Output: 5.5K · Cached: 677K

Review guidance: REVIEW.md from base branch main

@iscekic

iscekic commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator Author

(bot) Standin review — round 1 (reviewer-bot substitute per the workflow override; head reviewed: 6074e8ec5eaa6d604068c263cd1a35d162727edd, full diff vs main)

Findings (4), with orchestrator triage:

  1. Medium — drain()'s bound-expiry path has no automated coverage. The guarantee that the drain cannot wedge process exit inside the parent's unconditional 5 s terminate budget is implemented but never exercised; a regression removing the race/deadline would ship green and re-introduce mid-POST worker termination. → Accepted; repair in progress (fake-seam test: hanging flush, clock advanced past the 3 s bound, assert resolve + timeout log + no re-enqueue).

  2. Medium — no changeset for a user-facing @kilocode/cli fix. Repo AGENTS.md requires a patch changeset for user-facing fixes. → Accepted; repair in progress (.changeset/ingest-shutdown-flush.md).

  3. Low — §6.5's per-directory half is inspection-verified only, not unit-tested. The reviewer verified by inspection that no per-directory finalizer calls the drain (callers are exactly worker.ts, setup.ts, serve.ts; the KiloSessions finalizer only clears statusSyncs/disableRemote()). → Resolved by orchestrator-accepted note, per the reviewer's own offered resolution: an absence-of-hook property is not meaningfully unit-testable from an unrelated subsystem; inspection of the diff is the accepted control, and the module's comment (Do not call from per-directory instance finalizers — wrong granularity) plus this PR's reviewer trail are the tripwire. No code change.

  4. Low — freshShare/freshClient sit against the mandatory naming rule. Same camelCase-compound shape as the rule's avoid-example (existingClient). → Accepted; repair in progress (restructure to single-word fresh via small resolveShare/resolveClient closures).

Residual testing risks noted by the reviewer (no action): on-device E2E per ending runs as the next workflow stage; K4's interactive Ctrl-C claim rests on the raw-mode argument (K4 is droppable by design if the E2E shows a regression); the kilo serve daemon-stop path carries the plan's weaker-evidence label; cached-client fallback could POST once with a rotated-out token (recorded-residual class).

A fresh standin review will run on the repaired head; the loop repeats until a round reports no actionable findings on the exact latest head.

@iscekic

iscekic commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator Author

(bot) Standin review — round 2: No findings. (review of record; head reviewed: 621ebc89b8f06139cfaf1f215183ffd24d7110e8, full diff vs main)

A fresh standin reviewer re-reviewed the complete diff on the repaired head, including the round-1 repair commit (bound-expiry test, .changeset/ingest-shutdown-flush.md, single-word fresh naming via resolveShare/resolveClient), and returned no actionable findings. All three round-1 repairs are confirmed in place; finding 3 of round 1 stays resolved by the orchestrator-accepted note recorded above.

Residual testing risks carried by the reviewer (no action): on-device E2E per ending is the next workflow stage; the kilo serve daemon-stop call site carries the plan's weaker-evidence unit-level label; the first-ever-resolution-at-drain-time drop is the plan's recorded residual; a purely theoretical log.error-throw path was noted as not actionable.

The standin loop is converged on the exact latest head. On-device E2E results (/exit, SIGHUP, SIGINT, delayed-/exit control) will be posted as a follow-up (bot) comment with the running CLI's version stamp.

@iscekic

iscekic commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator Author

(bot) On-device E2E (plan §6.7/§6.8): ACCEPT — 11/11. Verified by a fresh mobile-e2e-verifier against the local stack, driving the locally built CLI (harness: Kilo-Org/cloud#4786) installed from this branch's build.

Binary exercised: 0.0.0-fix-cli-ingest-flush-on-shutdown-202607262141 (built from head 621ebc89b8), footer ◆ Remote, embedded-worker process model (KILO_NO_DAEMON=1 emitted by the harness). The two commits pushed after that build touch only the non-TUI setup.ts registration wrapper (lazy import, non-rejecting task) and its test mock — not the TUI/drain paths the endings exercise; the non-TUI path is unit-covered in cli-shutdown.test.ts.

Pass rates vs baseline (baseline loss measured pre-fix):

Ending Baseline loss Post-fix Result
/exit immediate 3/3 3/3 no loss fixed
SIGINT immediate 3/3 3/3 no loss fixed
SIGHUP 1/1 3/3 no loss fixed
/exit delayed ≥10 s (control) 0/2 2/2 complete unchanged

Per attempt: 3 turns ending in a unique TAILMARK-K-<ending><attempt>-3 marker on kilo-auto/efficient; ending applied after the final ingest sync and before any ingest flush ok (invalid attempts discarded and retried); fresh cold read-only mount on device (screenshotted, readonly=true, composer absent); scored server-side via cliSessionsV2.getSessionMessages judging the marker in assistant text parts only (the user prompt always contains the marker). Drain evidence captured per attempt: ingest flush + ingest flush ok covering part×4,message,session_close,session_status (items=7) after the ending was applied — the process held its exit open for the POST.

§6.8: the terminal batch is one POST (items=7), streaming parts still coalesce at ~1/s — no per-part POST storm; the delayed-/exit control is unchanged.

Residuals (unchanged from the PR body): kill -9/crash/mid-turn kills still lose an unflushed tail (window narrowed, not closed); daemon-attached client exit remains argued-unexposed, not measured; the kilo serve drain has no daemon-stop E2E (plan's weaker-evidence label).

@iscekic

iscekic commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator Author

(bot) Standin review — rounds 3–5 triage record (fresh reviewer each round, each on the exact then-latest head)

Round 3 (head 0865051a30) returned 2 Low findings:

  1. The lazy-imported drain task registered with KiloShutdown could reject and skip disposeAllInstances()accepted, repaired (bc34014e91: task wrapped in try/catch + log; new cli-shutdown test proves a rejecting drain does not block dispose).
  2. §6.5's per-directory negative half has no unit test → resolved by orchestrator-accepted note (same finding as round 1 Rename to kilo code in chats #3; an absence-of-hook property is not meaningfully unit-testable from an unrelated subsystem — inspection of the diff plus the export's warning comment is the accepted control; the reviewer verified by inspection that no per-directory finalizer calls the drain).
    Process note: the round-3 diff attachment mistakenly contained the companion cloud PR's diff; the reviewer caught it and reviewed the real kilocode diff from the worktree at the pinned head, so the round stands.

Round 4 (head bc34014e91) returned 1 Low finding: the KiloCli path did not pin the mandatory drain-before-dispose ordering (the drain mock recorded into a side counter, not the ordered call log) → accepted, repaired (b2540c4909: the mock now records into the ordered array and the first test expects drain ahead of dispose; a mutation test — swapping the two production lines — confirmed the assertion bites, then was reverted).

Round 5 (head b2540c4909) returned 3 Low findings:

  1. §6.5 negative-half unit test (third re-raise) → resolved by the same orchestrator-accepted note; this round's own required outcome allowed that resolution.
  2. A drain rejection would skip dispose/server-stop on the serve and worker paths (unguarded awaits) → accepted, repaired (ea6b7e5b80: the never-reject guarantee moved into the shared IngestDrain.create choke point with an injected error log, so all three call sites inherit it uniformly; new once-guard test pins resolve-on-rejection, logged once, no retry).
  3. The lazy-import rationale comment was factually wrong (kilo-sessions is already eagerly loaded at startup via cli/cmd/remote.ts) → accepted, repaired (same commit: comment now states the true reason — keeping setup.ts's own static import graph within the coverage of existing partial module mocks).

A fresh round-6 review is running on head ea6b7e5b80.

@iscekic

iscekic commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator Author

(bot) Standin review — convergence record (rounds 6–8) and review of record for head 37bb00db461826494cab084923da792c108ba323.

Round 6 (head ea6b7e5b80) returned 2 Low findings:

  1. §6.5 per-directory clause test — resolved by orchestrator-accepted note (see below).
  2. The retryable-HTTP-status-under-shutdown branch (ingest-queue.ts:339-347) had no coverage → accepted, repaired (0c3cc0f5a9: 429-under-drain test asserts the shutdown: true log and no re-enqueue).

Round 7 (head 0c3cc0f5a9) returned 1 Low finding: two cli-shutdown tests were silently coupled to declaration order (one-time KiloShutdown registration consumed by the first test) → accepted, repaired (37bb00db46: later tests install their own per-test drain task via installDrain() and assert drain-before-dispose uniformly; verified empirically that skipping the first test leaves the others green).

Round 8 (head 37bb00db46, exact latest head) returned exactly 1 Low finding — the §6.5 per-directory clause, its fifth independent raise. No other actionable findings.

Orchestrator acceptance for the §6.5 per-directory clause (final, recorded per the reviewers' own offered resolution): plan §6.5's second half ("a per-directory instance dispose or reload does not drain the global queue") is an absence-of-hook property. The drain is reachable only through the process-level KiloSessions.drainIngestForShutdown() export, whose comment explicitly forbids per-directory use; the KiloSessions finalizer (statusSyncs.clear() + disableRemote()) is byte-untouched by this PR; and five independent fresh reviewers verified by inspection that no dispose/reload path invokes the drain. A unit test of this negative would have to stand up the InstanceRuntime/Effect disposal stack to assert a non-call — brittle scaffolding disproportionate to the property, and the kilocode AGENTS.md testing rules require testing actual implementation rather than duplicating logic. Inspection of the diff, the export's warning comment, and this eight-round review trail are the accepted control. This finding is therefore not actionable and is recorded as resolved, not rejected on evidence.

With that resolution, round 8 has no valid findings on the exact latest head — the standin loop is converged. Summary of the loop: 8 fresh mobile-reviewer rounds (kimi-k3 high, full diff each), 11 actionable findings all repaired and re-verified (bound-expiry test, changeset, naming, non-rejecting task ×2 (task-level then shared-guard), ordering pin, lazy-import rationale, retryable-status coverage, order-decoupling, CI import-graph repair), one finding class resolved by this acceptance note.

Remaining evidence: CI on 37bb00db46 (posted separately when terminal); on-device E2E posted above (ACCEPT 11/11); the on-device run predates only the setup.ts registration-wrapper commits, which do not touch the TUI/drain paths it exercised.

@iscekic
iscekic merged commit b2735bf into main Jul 27, 2026
41 of 44 checks passed
@iscekic
iscekic deleted the fix/cli-ingest-flush-on-shutdown branch July 27, 2026 09:12
t7tran pushed a commit to t7tran/kilocode that referenced this pull request Aug 14, 2026
* fix(cli): drain session ingest queue on shutdown and flush terminal batches promptly

* fix(cli): drain the session ingest queue on process shutdown

* fix(cli): pin drain bound expiry, add changeset, conform to naming rule

* fix(cli): keep kilo-sessions out of the CLI startup import graph

* fix(cli): never let the ingest drain task reject the shutdown sequence

* test(cli): pin drain-before-dispose ordering on the KiloCli shutdown path

* fix(cli): make the guarded ingest drain non-rejecting and correct the lazy-import rationale

* test(cli): cover the retryable-status drain path under shutdown

* test(cli): decouple cli-shutdown drain assertions from declaration order
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