Bug report: `kimi web --rc` / TUI `/remote-control` intermittently exits silently with code 0, swallowing the real error

Bug report: kimi web --rc / TUI /remote-control intermittently exits silently with code 0, swallowing the real error

Environment

  • Kimi Code 2.0.2 (npm global install, @moonshot-ai/kimi-code)
  • Node.js v22.23.2
  • Ubuntu 22.04, Linux 6.8.0-137-generic x86_64
  • Auth: Kimi OAuth login (also reproducible with API-key/env auth)
  • Relay https://code-rc.kimi.com reachable (HTTP + WebSocket handshake OK)

Summary

When Remote Control fails to start, the process is supposed to print the error to
stderr and exit with code 1. Instead, intermittently (~1 in 3 runs) it exits with
code 0 and produces no output at all
, hiding the failure. In the TUI this presents
as: run /remote-control в†’ the whole session silently closes, no message.

Steps to reproduce

  1. Have a working kimi install. To force the failure path, use a machine without
    an OAuth login (e.g. API-key/env auth only) — startRemoteControl then throws
    Remote Control requires a Kimi login. Run kimi login first.

  2. Run repeatedly:

    for i in $(seq 1 10); do kimi web --rc --no-open; echo "exit=$?"; done
    
  3. Observe: most runs print the login error and exit 1, but some runs print
    nothing and exit 0.

Expected behavior

Every run prints the error message and exits with a non-zero code.

Actual behavior

Intermittent silent exit with code 0 (no stdout/stderr output).

Root cause analysis

Failure path in runServerInProcess (src/cli/sub/web/run.ts):

try {
  await hooks.onReady?.(running.address);   // startRemoteControl throws here
} catch (error) {
  try {
    await hooks.onShutdown?.("startup_failed");
  } finally {
    await running.close();
    await shutdownTelemetry({ timeoutMs: CLI_SHUTDOWN_TIMEOUT_MS });  // в†ђ dies here
  }
  throw error;   // never reached in the bad case
}
// handleWebCommand catch: stderr.write(message); process.exit(1);  // never reached

TelemetryClient.shutdown (packages/telemetry/src/client.ts) wraps sink.flush()
in an abort timeout whose timer is unref’d:

const timer = setTimeout(() => { controller.abort(); }, options.timeoutMs);
timer.unref?.();
await sink.flush(controller.signal);

When the telemetry flush pends without holding any ref’d event-loop handle (hanging
request / unref’d backoff sleep in abortableSleep / fetchWithTimeout), the event
loop drains while awaiting shutdownTelemetry. Node then performs a natural exit
with code 0
before the rethrow reaches handleWebCommand’s catch — so the error
message is never printed and the exit code is wrong.

Evidence gathered on the affected machine:

  • A beforeExit probe prints zero active handles (process._getActiveHandles() === [])
    at the moment of the silent exit — i.e. natural exit, not process.exit.
  • stderr markers inside the error path show the last reached step is
    await shutdownTelemetry(...); CATCH: reached never prints.
  • After removing the timer.unref?.() line, 6/6 runs printed the error and exited 1.

Related unref’d timers in the same shutdown path worth auditing:
fetchWithTimeout and abortableSleep (packages/telemetry/src/transport.ts).

Suggested fix

Do not unref the shutdown abort timer (or otherwise pin a reference, e.g.
process.ref/setInterval keep-alive) until CLI shutdown completes — the process
must stay alive while error handling is still in flight. More generally, any
cleanup path that ends in process.exit should not rely on natural-exit semantics.

Secondary issue: Remote Control ignores env-scoped OAuth logins

startRemoteControl (packages/remote-control/src/remote-control.ts) loads the token
via resolveKimiTokenStorageName({ providerName: "managed:kimi-code" }), which
resolves to the unscoped storage name kimi-code (file ~/.kimi-code/credentials/kimi-code.json).

When the user is on a custom env (headroom/proxy setups), kimi login stores the
OAuth token under a scoped key oauth/kimi-code-env-<hash> (file
kimi-code-env-<hash>.json). Remote Control then reports
“Remote Control requires a Kimi login” even though the user is logged in.

Suggested fix: fall back to / iterate over oauth/kimi-code-env-* storages (or
resolve the current env’s scoped key) when the unscoped token is absent.
Verified locally: the scoped token is accepted by the relay once it is loaded
(copying the scoped file to kimi-code.json makes RC register successfully).

Impact

Any startup failure of Remote Control (missing login, registration rejection,
lock conflict, relay unreachable) can manifest as a silent crash instead of a clear
error — including the TUI /remote-control handoff, where the user’s session just
vanishes.