fix(routes): match on pathname so ?token= query strings don't bypass routing#34
Merged
Conversation
Codex 0.133+ pings ~10 platform endpoints on startup (plugin lists, connector directory, app metadata, usage). They're internal RPC, not conversation data — but without filtering they create ~30 entries in the dashboard timeline before the first user prompt. The fix: - Extend isChatGPTCodexPath to include /v1/ps/plugins/* (was falling through to the Anthropic upstream and getting tagged provider:anthropic, agent:claude — a classification bug, not just noise). - Add isCodexPlatformNoisePath predicate covering plugins, ps/plugins, connectors, codex/apps, codex/usage. - In the request handler, short-circuit noise paths with skipEntry:true (same pattern as isQuotaCheck). Requests still get proxied so codex doesn't break; only entry creation and per-session counter bumps are skipped. Telemetry endpoint (/v1/codex/analytics-events/events) is intentionally kept visible — a follow-up will parse turn metadata (model, tokens, duration) out of it so codex entries become comparable to claude ones. Tested manually: 7 noise paths fire → 0 entries; telemetry POST → 1 entry. New unit tests cover the predicate and the /v1/ps/plugins classification fix. New e2e test asserts entries stay empty for noise paths while the telemetry endpoint still records. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… aren't truncated When ccxray (or its agent child) exits while a WebSocket upgrade is in flight, the storage writes for the WS entry race process.exit and end up as 0-byte _req.json/_res.json. The entry shows up in index.ndjson but its actual transport metadata is unrecoverable. Root cause: recordWebSocketEntry calls config.storage.write(...) and just .catch()s the promise — fire-and-forget. spawnStandaloneAgent's onExit calls process.exit(code) immediately after server.close(), and fs.writeFile hasn't flushed yet. The fix is a three-piece drain on shutdown: 1. server/storage/index.js wraps every adapter's async writes in an in-flight Set and exposes drain() — used after WS finalize so any queued fs.writeFile completes before process.exit. 2. server/ws-proxy.js tracks active sessions (so we can force-finalize stragglers when the agent exits before its WS close event fires) and pending recordWebSocketEntry promises. drainWebSocketProxy() force-finalizes any open pairs and awaits all in-flight entry writes. Idle-timeout and queue-cap semantics from f695539 are preserved verbatim. 3. server/index.js adds gracefulExit(code) that awaits drainWebSocketProxy then storage.drain, bounded by a 5s safety timeout. Wired into spawnStandaloneAgent's onExit, hub SIGTERM/SIGINT cleanup, hub idle shutdown (via hub.setOnShutdown), and standalone non-agent mode SIGTERM/SIGINT. New e2e test reproduces the race: opens a WS through the proxy, sends a frame, SIGTERMs the proxy, verifies _req.json + _res.json contain valid transport-only JSON with non-zero byte counts. Without gracefulExit the files would be 0-byte; with it they're complete. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…anism Both bugs that blocked Beta-readiness landed: - codex platform noise no longer pollutes the dashboard - WS entries survive proxy shutdown README gains a "Codex support (Beta)" section between Quick Start and Features. Calls out what the WS transport actually captures (frame and byte counts, not decoded content), what's still missing (token / model extraction from telemetry — Bonus follow-up), and the tunable env vars. CLAUDE.md gets two new bullets: - the codex platform path filter and where to find it - the gracefulExit / drainWebSocketProxy / storage.drain pipeline The ws-proxy.js and server/storage/ module rows pick up one-line notes about their shutdown contracts. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…routing Route handlers compared clientReq.url literally against '/_api/<route>', so AUTH_TOKEN-mode requests like /_api/entries?token=… missed every matcher and fell through to the upstream proxy — producing 404 noise, broken entry/token detail lookups, and dashboard pollution. Split off the pathname before comparing in hub.js + every route file under server/routes/. Add test/route-query-string.test.js as a regression net covering the literal-equality and regex-match cases. This is a prerequisite for the upcoming two-domain auth migration (reason/260525-0055-ccxray-auth-design/): the new dispatcher needs to classify requests by path, and that classification has to survive any query string the client appends. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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.
Summary
Route handlers compared
clientReq.urlliterally against'/_api/<route>', soAUTH_TOKEN-mode requests like/_api/entries?token=…missed every matcher and fell through to the upstream proxy — producing 404 noise, broken entry/token detail lookups, and dashboard pollution.Split off the pathname before comparing in
hub.jsand every file underserver/routes/. New regression test covers both the literal-equality cases (/_api/entries,/_api/settings,/_api/hub/*,/_api/costs/*,/_api/intercept/*,/_events) and the regex-match cases (/_api/entry/<id>,/_api/tokens/<id>).Why now
This is a prerequisite for the upcoming two-domain auth migration ([design doc to follow as docs/ PR]). The new dispatcher classifies requests by path, and that classification has to survive any query string the client appends.
Test plan
🤖 Generated with Claude Code