Skip to content

feat(paste): event-driven submit + per-paste debug dump + headless quirks docs - #71

Merged
Juliusolsson05 merged 2 commits into
mainfrom
feat-paste-debug-and-event-driven
May 11, 2026
Merged

Juliusolsson05 merged 2 commits into
mainfrom
feat-paste-debug-and-event-driven

Conversation

@Juliusolsson05

Copy link
Copy Markdown
Owner

Three coordinated changes addressing the long-standing "first Enter on a multi-line paste does nothing; second Enter submits" intermittent in Claude composers. Built on top of the PTY-isolated reproduction harness at `vendor/in_progress/paste-submit-repro/` and the per-session debug-dump pattern from PR #68.

⚠️ Depends on submodule PR Juliusolsson05/claude-code-headless#7 — merge that first, then bump this PR's submodule pointer to the new `main` SHA before merging here.

Track A — claude-code-headless documentation

Both comment-only changes land in the submodule (#7). Captures two findings the harness surfaced:

  • HeadlessTerminal.scheduleFlush stall under synchronized output — \\x1b[?2026h…\\x1b[?2026l sequences leave `pendingWrites` > 0 indefinitely; `'screen'` event silently stops firing even when the xterm buffer is being updated correctly. Recommend `snapshotPlain()` polling for correctness-critical consumers.
  • `'activity'` event false negatives — the bottom-up `SPINNER_VERB_RE` walk returns null during tool-result rendering and transient headers (Bash() preview, "Tip:" rows). Must NOT be used as a binary "did this submit?" verdict. The harness lost 2/10 iterations exactly this way before we switched to "did the placeholder clear from the composer?"

Track B — per-paste debug dump

Mirror of the dictation-debug subsystem (PR #68). Every Enter in a Claude composer mints a `pasteId` and writes append-only JSONL at `/paste-debug/.paste.jsonl` with RENDER / IPC / PTY / SCREEN / OUTCOME / ERROR events:

  • RENDER `keydown:enter`, `route:claude-paste-like` / `route:claude-plain-text` / `route:codex-bracketed-paste` / `route:claude-images`
  • IPC `write:paste-payload` / `write:submit-cr` with sha8 fingerprint of payload
  • PTY `main:write` with sha8 (pairs against renderer IPC events by sha8 + byte count, same way dictation-debug pairs chunks)
  • SCREEN `placeholder:appeared` / `placeholder:timeout` / `placeholder:no-session` with `waitedMs`
  • OUTCOME `submit:returned` / ERROR `submit:throw`

100 ms batched writes, 14-day startup prune, 0o600 file mode, no raw PTY bytes ever (sha8 fingerprints only). New files: `src/main/pasteDebugJournal.ts`, `src/main/ipc/pasteDebug.ts`, `src/preload/api/pasteDebug.ts`.

This is the diagnostic that will answer "why does the PTY-isolated harness pass 10/10 but the user still hits the bug live?" Hypotheses include IPC variance shrinking the effective gap, a double-fire on Enter where a second `\r` races the first paste's payload, or a React state mutation between paste and submit. The dump shape lets us pick the right one from a real failing session.

Track C — event-driven paste-submit

Replaces the unconditional 125 ms wall-clock wait in `claudePaste.ts:43` with a poll for Claude's `[Pasted text #N]` placeholder via the new `ClaudeCodeHeadless.awaitPastePlaceholder()`. The PTY-isolated harness shows event-driven is 10/10 reliable with an average wait of ~58 ms vs. the timer's 125 ms unconditional floor (which the harness shows races even at 1000 ms under load — there's no T that's reliably long enough).

Gated by `settings.eventDrivenPasteSubmit` (default ON). Falls back to the existing 125 ms timer after a 2 s safety bound if the placeholder never materializes (future-Claude rename insurance). Settings entry at Settings → Workspace → Event-Driven Paste Submit.

Test plan

  • `npm run build` clean (verified locally)
  • Harness reliability gate `runs/event-driven-reliability.ts` shows 10/10 PASS (verified locally)
  • Paste a multi-line message into a Claude composer; press Enter. The first Enter should submit reliably.
  • After a successful paste, find the dump at `~/Library/Application Support/cc-shell/paste-debug/.paste.jsonl`. Verify it contains the full event sequence: RENDER `keydown:enter` → RENDER `route:claude-paste-like` → IPC `write:paste-payload` (with sha8) → PTY `main:write` (matching sha8) → SCREEN `placeholder:appeared` (with `waitedMs`) → IPC `write:submit-cr` → PTY `main:write` (`\r`) → OUTCOME `submit:returned`.
  • Reproduce the user-facing "first Enter does nothing" bug if it still happens; the dump tells us exactly which event diverges from the expected shape above.
  • Flip `eventDrivenPasteSubmit` OFF; verify behavior reverts to pre-fix (the wall-clock 125 ms path).

Plan / context

`docs/superpowers/plans/2026-05-11-paste-submit-harness-findings-and-fix.md` has the full session log, hypothesis space, and 10-task breakdown. Harness at `vendor/in_progress/paste-submit-repro/` (untracked scratch, not committed).

🤖 Generated with Claude Code

Juliusolsson05 and others added 2 commits May 11, 2026 16:30
…irks docs

Bundles three coordinated changes addressing the long-standing
"first Enter on a multi-line paste does nothing; second Enter
submits" intermittent in Claude composers.

Track A — documentation in claude-code-headless submodule:
  - Thick WHY comment above HeadlessTerminal.scheduleFlush explaining
    that the 'screen' event silently stops firing under Claude's
    synchronized-output protocol (pendingWrites stays > 0 with
    @xterm/headless under sync-out pressure). Consumers needing
    correctness should poll snapshotPlain() directly.
  - Stronger warning on the activity-event debouncer that the
    SPINNER_VERB_RE walk produces false negatives during
    tool-result rows and transient headers, and the 'activity'
    event must NOT be used as a binary "did this submit?" verdict.
    The paste-submit harness lost 2/10 iterations on a spurious
    spinner read before we figured this out.

Track B — per-paste debug dump:
  Mirror of the dictation-debug subsystem from PR #68. Every Enter
  in a Claude composer mints a pasteId and emits
  RENDER/IPC/PTY/SCREEN/OUTCOME events into
  <userData>/paste-debug/<pasteId>.paste.jsonl. Renderer and main
  both write into the same file keyed by pasteId; payloads are
  sha8-fingerprinted so the IPC and PTY events can be paired across
  the renderer↔main boundary. 100 ms batched, 14-day prune.

  This is the diagnostic that will finally answer "why does the
  PTY-isolated harness pass 10/10 with the production 125 ms timer
  but the user still hits the bug live?" Hypotheses include IPC
  variance shrinking the effective gap, a double-fire on Enter
  where a second \r races the first paste's payload, or a React
  state mutation between paste and submit — the dump shape lets us
  pick the right one from a real failing session.

Track C — event-driven paste-submit:
  Replaces the unconditional 125 ms wall-clock wait between
  bracketed paste and \r with a poll for Claude's `[Pasted text #N]`
  placeholder. The PTY-isolated harness shows event-driven is
  10/10 reliable with an average wait of ~58 ms vs. the timer's
  125 ms unconditional floor. Falls back to the timer after a 2 s
  safety bound if the placeholder never materializes (future-Claude
  rename insurance). Gated by settings.eventDrivenPasteSubmit,
  default ON. Setting also exposed in Settings → Workspace.

Submodule bump:
  packages/claude-code-headless → feat/paste-submit-headless-fix
  e59235a — adds ClaudeCodeHeadless.awaitPastePlaceholder and the
  two doc comments above. Will need to be merged to submodule main
  before this cc-shell PR can merge.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Juliusolsson05
Juliusolsson05 merged commit ac0759a into main May 11, 2026
@Juliusolsson05
Juliusolsson05 deleted the feat-paste-debug-and-event-driven branch July 7, 2026 21:56
Juliusolsson05 added a commit that referenced this pull request Sep 27, 2026
workflow-mcp#71 merged after #63: one shared pollUntil and run waits that end
on every terminal status replace the 0.5 s counted polls that failed package
CI. Test-only (six test files), so the lockfile still needs no resync
(npm ci --dry-run accepts it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant