[BUG] Code tab writes UI render metadata (start_timestamp/stop_timestamp/flags) into transcript JSONL, causing unrecoverable API 400 that recurs after full sanitization

Status Open
Reported on v2.1.246
Maintainer reply None cached
Activity 11 comments · opened Aug 27, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Problem: our long-running Code tab sessions keep becoming permanently unusable in the middle of real work. Once it happens, every request in the session — including /compact — fails with the same 400, so the session cannot recover from inside. It has recurred within hours even after we sanitized every transcript file on the machine, so we are stuck doing repeated manual repairs with no way to prevent it.

Cause on disk: three JSON keys — start_timestamp, stop_timestamp, flags — appear on text content blocks inside the transcript JSONL files at %USERPROFILE%\.claude\projects\**\*.jsonl. When the request is built from that history, the API rejects it: 400 "Extra inputs are not permitted".

Sample block from a backup (text redacted):
{"start_timestamp":"2026-08-26T09:42:33.302643423Z","stop_timestamp":null,"flags":null,"type":"text","text":"[REDACTED]","citations":[]}

What happened (all 2026-08-26 to 08-27, JST):

  1. A long session (~941 messages) failed with 400 messages.940.content.0.text.start_timestamp. Retries, summary requests, and /compact all failed.
  2. The same error occurred in a new session and with a different Claude account on the same machine (position became messages.2).
  3. Reinstalling the Desktop app changed nothing.
  4. We found the keys in 10 JSONL files across 2 projects. After deleting start/stop_timestamp, the same position failed with .text.flags — a second foreign key in the same blocks.
  5. We whitelist-sanitized ALL 818 transcript files on the machine and verified zero structural occurrences of the three keys.
  6. The same day, the keys reappeared: 2 dirty files at first scan (both sessions had gone through our manual /compact procedure), 4 by repair time — the count grew across an app shutdown. Write timestamps: 18:18–18:42 JST.
  7. We sanitized those 4. Within the next hours, new 400s appeared in further sessions: messages.553, messages.1042 (while a background task was running), and messages.2 minutes after opening a session the next morning.

What Should Happen?

Strip these keys when building API requests — exactly as already happens for the caller key — or do not write them onto message content blocks at all. Sessions should never become permanently unusable because of app-written metadata. Until fixed, any supported way to prevent or auto-recover from this would help; currently there is none we could find (details below).

Error Messages/Logs

API Error: 400 messages.940.content.0.text.start_timestamp: Extra inputs are not permitted
API Error: 400 messages.2.content.0.text.flags: Extra inputs are not permitted
Error during compaction: API Error: 400 messages.2.content.0.text.start_timestamp: Extra inputs are not permitted
request_id: will attach on next occurrence

Steps to Reproduce

We have no from-scratch trigger recipe; the keys were written repeatedly during normal use (around our manual /compact runs and around session/app close; auto-compact involvement not isolated). To observe the broken state and the verified recovery:

  1. Windows Desktop app, Code tab, local Git repo sessions (our setup: Opus/Fable, permission-bypass, multiple parallel sessions with subagents).
  2. Work in long sessions; run /compact; close sessions/the app.
  3. Check %USERPROFILE%\.claude\projects\**\*.jsonl — text blocks carry start_timestamp / stop_timestamp / flags keys (samples attached).
  4. Any message in an affected session returns the 400 above; /compact fails too.
  5. Verified recovery: with the app closed, strip the three keys from the JSONL (whitelist: keep API-schema fields + caller), reopen the session — it works again. This restored every affected session, every time.

Claude Model

None

Is this a regression?

I don't know

Last Working Version

Unknown. First observed 2026-08-26 on engine 2.1.246.

Claude Code Version

2.1.246 (Claude Code), bundled in Claude Desktop MSIX shell 1.37937.1.0. /status and claude --version are unavailable in the Desktop Code tab; version read from %LOCALAPPDATA%\Packages\Claude_pzs8sxrjxfjjc\LocalCache\Roaming\Claude\claude-code\2.1.246\

Platform

Anthropic API

Operating System

Windows

Terminal/Shell

Other

Additional Information

What we verified, item by item:

  1. The keys exist as JSON keys on content blocks (structural jq check, not string match) in the transcript files; text/tool content around them is intact.
  2. Whitelist-stripping the keys and reopening restores affected sessions — done on 787 files, then 4 files, then per-session; recovery succeeded every time.
  3. Contrast: a caller key sits on content blocks in 786/818 files and has never produced a 400 here — so the request builder strips caller but not these three keys.
  4. Recurrence survives: full-disk sanitization (818 files), app reinstall, and switching Claude accounts on the same machine.
  5. Controlled marker test on a real 9.4MB transcript: we injected a synthetic start_timestamp key; our SessionEnd hook removed it at session close and the session was fully functional on reopen. (We did not send a request while the marker was present; all 400s above came from keys written by the app.)
  6. A SessionStart-time sanitize fires and stages a backup, but the in-place file replace does not complete at session start (lock or concurrent append — not isolated). SessionEnd-time sanitize works reliably; this is our current workaround.
  7. ANTHROPIC_BASE_URL is not picked up by the Desktop Code tab in our tests (settings.json env block, and a persisted user environment variable verified in a fresh shell; app fully restarted) — so a local sanitizing proxy cannot be wired in the GUI.
  8. A custom PreCompact hook also runs in these sessions (not present in user-level settings.json). A disabled-hook control run is pending; we will post the result in a comment.

Environment: Windows 11 build 26200.9106; Desktop app Code tab (GUI), engine Desktop-managed/auto-updated.

Related issues (same error class): #19513 (thinking.valid — closest), #4378 (tool_use.text), #37452 (compaction corruption), #50375, #62396 (transcript-sanitization precedents).

Attached: claude-issue-evidence.txt — redacted contaminated-block samples from timestamped backups of 4 affected sessions.

View original on GitHub ↗

11 Comments

foma-agent · 4 days ago

Two hops, not one store.

The caller contrast already shows the request builder can strip keys. The 400s are the three keys that still ride the request. A zero-occurrence grep after sanitizing 818 files is a store receipt. The same-day rewrite (2 dirty files, then 4, then new 400s after the SessionStart in-place replace failed to complete) is the writer.

SessionEnd sanitize restoring a session is recovery, not a stop-writing receipt. A SessionStart sanitize that stages a backup but leaves the live file dirty is the earlier hop: downstream only sees what the writer already left.

I did not run Desktop.

Acceptance:

  1. Dirty jsonl on disk with start_timestamp / stop_timestamp / flags still 200s because request-build strips them the way caller is stripped.
  2. After a Code-tab turn, those three keys are absent from the session jsonl (stop-writing).
  3. /compact on a previously-400 session succeeds without a manual disk rewrite.
  4. A SessionStart sanitize that stages a backup but leaves the live file dirty fails closed as writer-not-stopped, not as "sanitized."

#19513 is the same extra-inputs class on thinking.valid. This report's witness is that sanitizing the store does not stop the writer.

katakana3 · 4 days ago

Additional occurrences + real request_ids (2026-08-27):

Latest event: API Error: 400 messages.445.content.0.text.start_timestamp: Extra inputs are not permitted
The session recorded three request_ids for it (retries), preceded by the same error at messages.424:
req_011CeSfgbVB1UJj6ERpTx78w / req_011CeSfsX1MBDT4PBn6Hz5wZ / req_011CeSgMDYQLy8WQK86z9zoo
Model shown: Fable 5; Desktop Code tab, engine 2.1.246, permission-bypass. Screenshot attached.

Scale correction: grepping the last ~24h of transcript JSONL for the error string shows it recorded at 30 distinct message positions across ~18 transcript files (some being parent/subagent duplicate copies) — including 5+ fresh sessions failing at messages.1–2. The "6+ events" in the report was an undercount.

Real request_ids for the events cited in the body (superseding "will attach on next occurrence"):

  • messages.940 (start_timestamp): req_011CeQSUmTqbqzwdTgHxFY8R
  • messages.2 (flags): req_011CeQuvurysP3d6Y3dpkqhk
  • messages.553 (start_timestamp): req_011CeRH8iGkcyRnFfe1AuTvt
  • messages.1042 (start_timestamp): req_011CeRL8Ps5CwZNrgMJg9akY

Observability note: the Code tab UI (error card and its "show details" view) never surfaces a request_id, but it IS recorded in the session transcript JSONL — which is how we extracted the above. Surfacing it in the error card would make Desktop-side reports much easier.

These occurred with our SessionEnd sanitize hook deployed — consistent with the triage framing: sanitizing the store does not stop the writer.
Recovery via the documented workaround (close session → SessionEnd whitelist sanitize → reopen) worked again.
The card appeared right after a background-task-stop notice and a user "continue" message, with the context indicator at max.
<img width="635" height="434" alt="Image" src="https://github.com/user-attachments/assets/e2619a4c-0959-44f8-89d2-7a1209361b5a" />

foma-agent · 4 days ago

The messages.1–2 failures on fresh sessions are the writer, not leftover dirty files. A SessionEnd sanitize that restores a session can still 400 on the next Code-tab session's first request. Store sanitize remains recovery.

The 30 positions / ~18 files (including parent/subagent copies) is volume. The keys are unchanged: start_timestamp / stop_timestamp / flags still ride the request; caller is already stripped.

Add to acceptance:

  1. A brand-new Code-tab session must not 400 Extra inputs on those three keys at messages.1–2.
  2. After background-task-stop + continue at max context, a Code-tab turn must not rewrite them onto the next request.

The missing request_id on the error card is a separate observability bug. The JSONL already records it.

Still 2.1.246. I did not run Desktop.

katakana3 · 3 days ago

Follow-up on the pending control experiment (PreCompact hook):

The custom PreCompact hook is ruled out as a cause. We removed it from
settings.json at 11:45 JST on 08-27. A session created fresh at 18:08 JST
(so it never ran that hook) still got the three keys written — the
contaminated block's own start_timestamp value reads
2026-08-27T09:19:55Z (18:19:55 JST) — and the session failed with
400 messages.92.content.0.text.start_timestamp
(request_id req_011CeT1XJ6joWJ4sYHFgGgsk, retry req_011CeT1kQ6G8ZtVwyEdvWDq9).

Frequency data point: on 08-27 alone, 13 of 14 sessions on this machine were
contaminated (measured from the one-generation backups our SessionEnd
sanitizer takes before cleaning — most contaminations were silently repaired
at session close and never surfaced as a 400). In every case the contaminated
line sat within the last 35 lines of the transcript at close time.

foma-agent · 3 days ago

PreCompact ruled out is the right control. A hook that never ran cannot be the writer.

The fresh 18:08 JST session (never had that hook) still got start_timestamp at 2026-08-27T09:19:55Z and 400'd at messages.92 (req_011CeT1XJ6joWJ4sYHFgGgsk). That is a Code-tab write during the session.

13/14 sessions contaminated on 08-27, with the dirty line always in the last 35 lines at close, is the rate the SessionEnd sanitize had been hiding. Silent close-time repair is not a stop-writing receipt. The 400s are only the sessions that continued after the write.

Acceptance add:

  1. A Code-tab session that never registered PreCompact still must not write those three keys.
  2. SessionEnd sanitize must not be the only path that makes the next turn 200.
  3. A late-session write in the last 35 lines still fails closed as writer-not-stopped, even if close-time sanitize would have cleaned it.

I did not run Desktop.

nttd-matsumototth · 3 days ago

Also affected, on the same Windows build (11 25H2, 26200.9106).

My reproduction is far simpler than the original report, which may help
narrow the cause: it fails on the 2nd message of a brand-new session,
with no long history, no /compact, and no custom hooks.

Steps to reproduce

  1. Open a new session (Desktop app Code tab, or claude in a terminal)

with any project folder.

  1. Send any message, e.g. hello. It succeeds normally.
  2. Send a second message, e.g. how are you.
  3. The second message always fails with:

API Error: 400 messages.2.content.0.text.start_timestamp: Extra inputs are not permitted

Reproduced 10+ times today. Failure rate: 100%.

Scope

| Environment | Result |
|---|---|
| Desktop app, Code tab (shell 1.37937.3) | Fails |
| Claude Code CLI, native install | Fails |
| Desktop app, Cowork mode | Works |
| claude.ai (web) | Works |

Variables ruled out

  • Model — reproduced on Sonnet, Opus and Fable.
  • Project folder — reproduced with more than one folder, including

a freshly created one.

  • Custom hooks — I have none configured.
  • CLI version — reproduced on more than one version. I did not

record the exact version numbers, so please treat this point as
weaker than the others.

Tried without effect

  • Full uninstall and reinstall of the Desktop app.
  • Deleting .claude.json.
  • Stripping the three keys from the JSONL transcripts. This restores

the session once, but the error returns afterwards — matching the
recurrence described in the original report.

Observation

The original report associates the writes with long sessions, /compact
runs, and session close. In my case the 400 appears on the second
message of a fresh session, so at least on this machine the metadata is
already present after a single exchange. I have not inspected the JSONL
at that exact moment to confirm when the keys are written, so this is
an observation about timing rather than about the write path itself.

foma-agent · 3 days ago

The 100% messages.2 failure on a brand-new session is the tighter timing bound. SessionEnd has not run between the first 200 and the second 400. The writer is the hop that persists turn 1.

Native CLI failing with Code tab, while Cowork and claude.ai 200, is a serializer split — not an API-host split and not Code-tab-only. Those two attach start_timestamp / stop_timestamp / flags onto transcript text; Cowork/web do not.

Uninstall, deleting .claude.json, and a one-shot jsonl strip that 400s again already match the store-vs-writer split.

Acceptance add:

  1. After turn 1 of a brand-new native-CLI or Code-tab session, the jsonl must not contain those three keys before turn 2 is sent.
  2. Cowork/web remaining 200 is not a Code-tab/CLI stop-writing receipt.

I did not run Desktop or Windows.

nttd-matsumototth · 3 days ago

Thanks — the timing bound you drew (writer = the hop that persists turn 1) told me exactly where to look. Here is that measurement, taken before turn 2 was sent. Acceptance item 8 fails: the keys are already there.

Setup

Brand-new Desktop Code tab session. Sent hello, got a reply, then read the jsonl. Turn 2 had NOT been sent.

entrypoint : claude-desktop
version : 2.1.246
shell : Desktop 1.37937.3
OS : Windows 11 25H2, build 26200.9106
cwd : C:\Users\<user> (no project files involved)
hooks : none configured

What the file contains

11 records. The user record is clean. Only the assistant record carries the three keys.

Assistant record, redacted — ids and uuids replaced, structure verbatim:

{"parentUuid":"<uuid>","isSidechain":false,
"message":{
"id":"msg_<redacted>","type":"message","role":"assistant",
"parent_uuid":"","uuid":"<hex>",
"content":[{"start_timestamp":"2026-08-28T00:26:41.175899535Z",
"stop_timestamp":null,
"flags":null,
"type":"text",
"text":"cached data for request <uuid> not found\n*******\nHello! How can I help you today?",
"citations":[]}],
"stop_reason":"end_turn","stop_sequence":null,
"usage":{...},"stop_details":null},
"requestId":"req_<redacted>","type":"assistant","uuid":"<uuid>",
"timestamp":"2026-08-28T00:26:41.196Z","effort":"high",
"userType":"external","entrypoint":"claude-desktop",
"cwd":"C:\\Users\\<user>","sessionId":"<uuid>",
"version":"2.1.246","gitBranch":"HEAD"}

Sending turn 2 in this session then produced the usual API Error: 400 messages.2.content.0.text.start_timestamp.

One more thing in the same block

The text field is contaminated too. It opens with cached data for request <uuid> not found, then a ******* separator, and only after that the actual reply. That prefix is not model output and is rendered verbatim in the app UI. It may travel the same path as the three keys, but I have not verified that.

foma-agent · 3 days ago

The pre-turn-2 jsonl closes item 8 as fail. The assistant text block already has start_timestamp / stop_timestamp / flags after turn 1. SessionEnd has not run. I did not run Desktop.

The cached data for request … not found prefix is a second contamination in the same block. Extra inputs 400s the three keys; the prefix lives in text, so request-build accepts it and the UI renders it. A first-turn 200 with that prefix is still a dirty transcript, just a schema-legal one.

Do not treat the prefix as the same writer until a session has the keys without the prefix, or the prefix without the keys. Score {keys, text_prefix, turn2_400} separately.

Acceptance add:

  1. After turn 1, the assistant text must not contain a non-model prefix such as cached data for request … not found.
  2. Keys without the prefix, or the prefix without the keys, is not a same-writer receipt.
nttd-matsumototth · 3 days ago

Agreed on scoring them separately. Reporting what I have, not a conclusion.

Across every session I have looked at so far, all three score together: keys present, prefix present, turn 2 400s. I have not produced a session with keys but no prefix, or a prefix but no keys, so I cannot settle the same-writer question from this end. Item 11 stays open as far as my data goes.

foma-agent · 3 days ago

Co-occurrence across every session you have is still not a same-writer receipt. It is a bound on this serializer: the two contaminations have fired together so far.

You do not need a naturally split session to separate the Extra-inputs 400 from the prefix. On a dirty jsonl you already have:

  1. Strip only start_timestamp / stop_timestamp / flags, leave the cached data for request prefix. Turn 2 should 200 — the prefix is schema-legal.
  2. Strip only the prefix, leave the three keys. Turn 2 should still 400 Extra inputs.

If (1) 200s and (2) 400s, the Extra-inputs hop is the keys even when the prefix always rode along. Item 11 stays open until a session is written with one and not the other.

I did not run Desktop.