Repository navigation
fix(kimi): only probe the endpoint kimi web announces - #4648
Merged
Merged
Conversation
A port TraceDecay asks `kimi web` for, but that `kimi web` never bound, can belong to any local process. The new `foreign` fake mode has another server take that port while Kimi never announces, and asserts Kimi's bearer token never reaches it. Fails on master with "GET /api/v1/plugins returned HTTP 404", the CI message from #4638. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TraceDecay picked a loopback port by binding port 0 and dropping the listener, passed it to `kimi web --port`, and polled it with Kimi's bearer token as soon as `server.token` existed. Until Kimi bound that port, any local process could hold it, and Kimi itself moves to the next free port when the requested one is busy. Either way Kimi's token went to a server that was not Kimi; in CI a parallel test's server answered 404. Start `kimi web --port 0` so only Kimi holds its port, and probe only the port and token from a complete `Local:` line of that child's ready banner, which Kimi prints after it binds. The `server.token` read goes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
ScriptedAlchemy
enabled auto-merge (squash)
October 31, 2026 03:01
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Root cause
refresh_installed_pluginpicked a loopback port by binding port 0, readinglocal_addr(), and dropping the listener. It then startedkimi web --no-open --port <p>and polledhttp://127.0.0.1:<p>/api/v1/pluginswithAuthorization: Bearer <token>as soon as a token existed. The token came from~/.kimi-code/server.token, which exists before Kimi starts, so polling began immediately.Nothing tied port
<p>to the spawnedkimi web:<p>(or forever, if it hangs or fails to start), any local process can take it.listenWithPortRetryinMoonshotAI/kimi-codepackages/kap-server/src/start.tsretriesport + 1up to 100 times onEADDRINUSE, so even a healthy Kimi can serve somewhere other than the port TraceDecay probes.In CI (PR #4638, job 124382047562), the fake
kimi webwas inhangmode. A parallel test's HTTP server took the freed port and answered 404. The product reported'kimi web' answered unexpectedly: GET /api/v1/plugins returned HTTP 404instead ofdid not serve its plugin API within 20 seconds.Security impact
On a real machine, the product sent Kimi Code's persistent bearer token to whatever local process held that port. Kimi's server API covers sessions, the filesystem, and shell access. So did any process that could win a port race, or that already held the port Kimi moved past. CI only showed the flaky symptom.
Fix
kimi web --no-open --port 0. Real Kimi accepts port 0:parsePortallows 0, andlistenWithPortRetrybinds the OS-assigned port and reports it as the bound port. Only that child then holds its port.Local: http://127.0.0.1:<port>/#token=<token>line in this child's own output. That's the line Kimi'sformatReadyBannerprints inonReady, after it has bound. Partial lines are ignored, so a split read can't produce a truncated token. The URL is always built from the fixed127.0.0.1origin plus the announced port.server.tokenfile read. Kimi prints the token from that same file in the banner, so the banner is the one source and carries the port with it.NotReady,Exited,Unauthorized, ...) are unchanged. Before the announcement, theNotReadydetail isit has not announced its local URL.spawn_host_serverhas no other caller. Every other port-0 bind in the workspace is a test fixture that keeps its listener open.docs/USER-GUIDE.mdnow describes the announced-endpoint handshake.Evidence
The new
foreignfake mode starts a separate local HTTP server on the port the product passed to--port. That server answers 404 and records eachAuthorizationheader. The fakekimi webitself never announces.Before (commit 1 on master's product code,
bazel test //crates/tracedecay-cli:host_journeys_suite --test_arg=host_lifecycle_cli_acceptance::kimi_web_refresh::):That's the same message CI reported.
After (commit 2, full suite):
Verification
bazel test //crates/tracedecay-agent-hosts:all: 3/3 targets pass. Two new unit tests cover banner parsing: the fullLocal:line, a partial line, and a line with no token.bazel test //crates/tracedecay-cli:host_journeys_suite: 50 passed.bazel build --config=clippy //crates/tracedecay-agent-hosts:all //crates/tracedecay-cli:host_journeys_suite: clean with-Dwarnings.rustfmt_check_2024for both crates: clean.gen_builds --checkdoesn't apply.--port 0behavior were checked againstMoonshotAI/kimi-codesource (apps/kimi-code/src/cli/sub/web/{run,access-urls,shared}.ts,packages/kap-server/src/start.ts).🤖 Generated with Claude Code