Skip to content

Session resume permanently broken by combined large events.jsonl + U+2028 corruption (reproducer + workaround) #2490

@KarnikJain

Description

@KarnikJain

Bug Summary

Session 5fecb21a-8cde-4fa1-ac96-934505967671 became permanently unresumable due to two independent bugs compounding:

  1. Long-lived session shows as corrupted on resume despite valid events.jsonl #2209events.jsonl grew to 20MB / 10,667 events (10 session segments). CLI cannot replay this on resume.
  2. 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.start is present
  • Suggestion: "Session has N events (XMB). Try: copilot session repair "

Fix 4: copilot session repair command

Add a repair subcommand that:

  1. Validates all JSONL lines
  2. Fixes U+2028/U+2029 escaping
  3. Ensures session.start is first event
  4. Trims to last N events if oversized
  5. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions