diff --git a/src/server/request-log-conversation.ts b/src/server/request-log-conversation.ts index 9f8d8a67dec..75642e07101 100644 --- a/src/server/request-log-conversation.ts +++ b/src/server/request-log-conversation.ts @@ -286,8 +286,8 @@ export function conversationIdFromClaudeMetadata( metadata: { user_id?: unknown } | null | undefined, ): string | undefined { if (!metadata || typeof metadata.user_id !== "string") return undefined; - // Too long to persist: skip parsing a blob normalizeLogConversationId rejects anyway. - if (metadata.user_id.trim().length > LOG_CONVERSATION_ID_INPUT_MAX) return undefined; + // No length guard before stripping tk: an oversized tk still leaves a valid session id, + // which also gates Claude routing identity (claude-messages.ts claudeNativeSessionId). return normalizeLogConversationId(claudeSessionUserId(metadata.user_id)); } diff --git a/structure/dashboard-and-usage.md b/structure/dashboard-and-usage.md index 9c27768c993..b2357029899 100644 --- a/structure/dashboard-and-usage.md +++ b/structure/dashboard-and-usage.md @@ -570,7 +570,7 @@ re-validated on read; the Logs list shows it as a text badge, the detail dialog `src/server/request-log-filter.ts` owns the `/api/logs` query filters including `protocolMode`; its single-pass query applies provider, conversation, model, account, protocol mode and status before `tail`, then reports the pre-pagination count alongside the offset/limit page. `src/server/request-log-filter.ts` accepts the full provider/model label shown by `ocx logs` and bare model IDs, including IDs containing slashes. Qualified failover matches bind provider/model from the same attempt; matching occurs before the row cap and pagination. [Protocol Paths](data-planes/protocol-paths.md) owns its derivation. -Conversation searches in the dashboard, `/api/logs` and `ocx logs filter --conversation` accept `codex://threads/` links up to 512 characters, including query/fragment metadata, trailing slashes and surrounding whitespace. The server matcher in `src/server/request-log-conversation.ts` and its browser mirror in `gui/src/log-conversation-id.ts` try both the extracted single-segment id and the original trimmed query, preserving raw-id and whole-literal-URI digest matching. The prefix is case-insensitive; the id retains its case. Malformed or longer links remain plain queries subject to the existing 4096-character digest-input bound. A pasted Claude Code `metadata.user_id` adds one more candidate, the JSON object without its per-turn `tk` (`claudeSessionUserId`), because the Messages inbound stores the session under that form; any turn's `user_id` therefore finds the whole session, and the raw candidate still matches rows logged before `tk` was stripped. A query over 4096 characters is not parsed, and `conversationIdFromClaudeMetadata` skips a `user_id` over that bound before parsing, while the inbound cache key still drops `tk` at any length. Persistence is unchanged. +Conversation searches in the dashboard, `/api/logs` and `ocx logs filter --conversation` accept `codex://threads/` links up to 512 characters, including query/fragment metadata, trailing slashes and surrounding whitespace. The server matcher in `src/server/request-log-conversation.ts` and its browser mirror in `gui/src/log-conversation-id.ts` try both the extracted single-segment id and the original trimmed query, preserving raw-id and whole-literal-URI digest matching. The prefix is case-insensitive; the id retains its case. Malformed or longer links remain plain queries subject to the existing 4096-character digest-input bound. A pasted Claude Code `metadata.user_id` adds one more candidate, the JSON object without its per-turn `tk` (`claudeSessionUserId`), because the Messages inbound stores the session under that form; any turn's `user_id` therefore finds the whole session, and the raw candidate still matches rows logged before `tk` was stripped. A query over 4096 characters is not parsed. `conversationIdFromClaudeMetadata`, like the inbound cache key, drops `tk` at any length before applying that bound, so an oversized `tk` keeps the session id that also gates Claude routing identity. Persistence is unchanged. Rows may also carry `genStartMs` and `lastOutputMs`, an observed request-relative pair recorded by `src/server/request-log-generation-window.ts` from the Responses and Anthropic SSE taps: the first output item or content block (reasoning included) to the last output delta. They are proxy observation times, not provider-internal token timing, and `src/usage/log.ts` keeps them only as a pair of nonnegative finite values with `lastOutputMs >= genStartMs`. `decodeTokPerSecondResult` in `src/server/management/shared.ts` prefers that window when the pair is valid and otherwise falls back to the post-TTFT window (`durationMs - firstOutputMs`); both keep the one-second minimum, the `ttft_missing` / `decode_window_too_short` unavailable reasons and `estimated: true`, so a short measured window is still an estimate rather than a provider decode benchmark. The end-to-end tok/s column is unchanged, attempts carry no generation window of their own, and the request-history view does not present one. Successful decode DTOs additionally expose `timingBasis: "generation-window" | "legacy-post-visible-output"`, derived at read time only. Logs labels the method “Generation window” or “After visible output” in the list and attempt table and uses “Output rate during generation (est.)” or “Output rate after first visible output (est.)” in request details. Missing or unfamiliar bases show “Timing unknown” and “Output rate (est.; timing method unknown)” in details rather than inferring a method. The detail grid shows a full-width muted caption explaining the method only when the decode rate is available. Both numerators include reported reasoning output tokens, so the two estimates are not directly comparable. Historical JSONL and end-to-end rates are unchanged. Request-history selectors longer than 130 characters persist as a prefix plus a digest of the complete diff --git a/tests/usage/request-log-conversation.test.ts b/tests/usage/request-log-conversation.test.ts index 494b134efe8..b4a77351f79 100644 --- a/tests/usage/request-log-conversation.test.ts +++ b/tests/usage/request-log-conversation.test.ts @@ -266,6 +266,8 @@ describe("conversationIdFromClaudeMetadata", () => { const huge = JSON.stringify({ session_id: "s", tk: "t", pad: "x".repeat(5000) }); expect(conversationIdFromClaudeMetadata({ user_id: huge })).toBeUndefined(); expect(JSON.parse(claudeSessionUserId(huge))).not.toHaveProperty("tk"); + // An oversized tk alone does not cost the session its identity (it also gates routing). + expect(conversationIdFromClaudeMetadata({ user_id: turn("t".repeat(5000)) })).toBe(digest32(session)); }); });