-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Session resume permanently broken by combined large events.jsonl + U+2028 corruption (reproducer + workaround) #2490
Description
Bug Summary
Session 5fecb21a-8cde-4fa1-ac96-934505967671 became permanently unresumable due to two independent bugs compounding:
- Long-lived session shows as corrupted on resume despite valid events.jsonl #2209 —
events.jsonlgrew to 20MB / 10,667 events (10 session segments). CLI cannot replay this on resume. - Session file corrupted: raw U+2028/U+2029 in events.jsonl breaks JSON.parse() on /resume #2012 — Raw U+2028 (Unicode Line Separator) characters in a user message (line 10391 of original) break the JSONL line parser.
Fixing either bug alone is insufficient — both must be resolved for the session to resume. This makes the failure especially confusing: after fixing U+2028, the "corrupted or incompatible" error persists with no useful diagnostic.
Environment
- Copilot CLI: 1.0.16
- Model: Claude Opus 4.6 (1M context)
- OS: macOS (Darwin arm64)
- Session created: 2026-03-30, last used: 2026-04-02
- Session purpose: I3C Prodigy dongle automation (heavy tool use: SSH, Playwright, code generation)
Session Diagnostics
| Metric | Value |
|---|---|
| Session ID | 5fecb21a-8cde-4fa1-ac96-934505967671 |
| events.jsonl size | 20,287,693 bytes (20MB) |
| Total event lines | 10,667 |
| Session segments | 10 (start + 9 resumes) |
| User messages | 159 |
| Tool executions | 1,713 |
| Premium requests | 36 (last segment) |
| Largest single event | 1,689,631 bytes (1.6MB — a hook.start containing SharePoint binary file data) |
| U+2028 occurrences | 4 (all in one user.message event — pasted text) |
Error Progression
Error 1 (original — before any fix):
✗ Failed to resume session
CLI exits immediately — no specific error. The events.jsonl was too large to process.
Error 2 (after trimming to 2,175 events / 2.9MB):
✗ Failed to resume session: Error: Session file is corrupted
(line 1899: SyntaxError: Unterminated string in JSON at position 833)
Now the U+2028 bug surfaces (previously masked by the size issue).
Error 3 (after fixing U+2028 with naive string replace):
✗ Failed to resume session: Error: Session file is corrupted or incompatible
The content.replace("\u2028", "\\\\u2028") over-escapes, producing \\u2028 literal text in the JSON instead of the proper \u2028 escape sequence.
Error 4 (after proper U+2028 fix via json.dumps(ensure_ascii=True) + re-trim):
✗ Failed to resume session: Error: Session file is corrupted or incompatible
Trimming removed the session.start event. The CLI requires it as the first line.
Final fix (prepend original session.start event):
Session resumes successfully.
Root Causes & Proposed Fixes
Fix 1: Event log rotation / size cap
events.jsonl should not grow unboundedly. Proposed:
- Hard cap: When events.jsonl exceeds 5MB or 3,000 events, automatically create a checkpoint and truncate old events on next session start.
- Per-event cap: Reject or truncate individual events > 500KB (the 1.6MB SharePoint binary hook event is unnecessary for replay).
- Checkpoint-aware trimming: On resume, if events exceed threshold, load from the latest checkpoint instead of replaying all events.
Fix 2: Escape U+2028/U+2029 on write
When writing events to events.jsonl, pass all string content through JSON.stringify() which properly escapes U+2028/U+2029 as \u2028/\u2029. The bug is that raw Unicode codepoints are valid in JSON strings per RFC 8259, but JavaScript JSON.parse() on a per-line basis treats them as line terminators, splitting the JSONL record.
// In the JSONL writer:
const line = JSON.stringify(event);
// JSON.stringify already escapes U+2028/U+2029 in JS
fs.appendFileSync(eventsPath, line + "\n");Alternatively, add a post-stringify sanitization:
function sanitizeJsonLine(json) {
return json.replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
}Fix 3: Better error diagnostics
The generic "corrupted or incompatible" error should include:
- Which line failed parsing (already done for SyntaxError)
- File size and line count
- Whether
session.startis present - Suggestion: "Session has N events (XMB). Try: copilot session repair "
Fix 4: copilot session repair command
Add a repair subcommand that:
- Validates all JSONL lines
- Fixes U+2028/U+2029 escaping
- Ensures
session.startis first event - Trims to last N events if oversized
- Reports what was fixed
Manual Workaround (working)
SESSION_DIR=~/.copilot/session-state/<SESSION_ID>
# 1. Backup
cp "$SESSION_DIR/events.jsonl" "$SESSION_DIR/events.jsonl.bak"
# 2. Save the session.start event
head -1 "$SESSION_DIR/events.jsonl" > /tmp/session_start.jsonl
# 3. Trim to last ~900 events
tail -n 900 "$SESSION_DIR/events.jsonl.bak" > "$SESSION_DIR/events.jsonl.trimmed"
# 4. Fix U+2028/U+2029
python3 -c "
import json
with open(\"$SESSION_DIR/events.jsonl.trimmed\") as fin:
lines = fin.readlines()
with open(\"$SESSION_DIR/events.jsonl\", \"w\") as fout:
# Prepend session.start
with open(\"/tmp/session_start.jsonl\") as ss:
fout.write(ss.read())
for line in lines:
if \"\u2028\" in line or \"\u2029\" in line:
obj = json.loads(line)
line = json.dumps(obj, ensure_ascii=True) + \"\n\"
fout.write(line)
"
# 5. Resume
copilot -s <SESSION_ID>Related Issues
- Long-lived session shows as corrupted on resume despite valid events.jsonl #2209 — Large events.jsonl causes corrupted error (same root cause, different session)
- Session file corrupted: raw U+2028/U+2029 in events.jsonl breaks JSON.parse() on /resume #2012 — U+2028/U+2029 breaks JSONL parsing (exact same bug)
- Copilot cli needs to be more resilient to crashes/unexpected shutdown and recover from them better. #2217 — Session resilience to crashes
- Session resume permanently broken: tool_use/tool_result mismatch from interleaved sub-agent turns #2323 — Tool use mismatch from interleaved sub-agents