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.comreachable (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
-
Have a working
kimiinstall. To force the failure path, use a machine without
an OAuth login (e.g. API-key/env auth only) —startRemoteControlthen throws
Remote Control requires a Kimi login. Runkimi loginfirst. -
Run repeatedly:
for i in $(seq 1 10); do kimi web --rc --no-open; echo "exit=$?"; done -
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
beforeExitprobe prints zero active handles (process._getActiveHandles() === [])
at the moment of the silent exit — i.e. natural exit, notprocess.exit. - stderr markers inside the error path show the last reached step is
await shutdownTelemetry(...);CATCH: reachednever 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.