Repository navigation
dor send prompt arrives truncated in Claude Code (missing 1,022-byte prefix) #679
Description
A long prompt sent through dor send --raw --text ... --key enter reached Claude Code with its beginning missing. The CLI reported success. Claude explicitly confirmed receiving only the second half of the brief and therefore did not run the intended /simplify task until a shorter retry was sent.
Observed September 16, 2026, around 20:27–20:29 America/Los_Angeles, while developing workspace tabs in PR #678. This issue records the evidence only; no fix is included in that PR.
Environment
- macOS; sending shell: zsh.
- Repository/worktree:
/Users/ntwigg/projects/dormouse.workspace-tab, HEAD01825050at the time. - Target:
surface:7, created usingdor ensure --cwd /Users/ntwigg/projects/dormouse.workspace-tab -- claude --model fable. - Target UI: Claude Code v2.1.274, Fable 5.1 with high effort, Claude Max; auto mode enabled.
- A preceding
dor read surface:7 --scrollback --lines 80showed the correct working directory and an idle prompt.
Exact first send
The tool call used exec_command, with workdir set to the worktree above, and this command (the prompt is one line of ASCII):
dor send surface:7 --raw --text '/simplify and commit the complete workspace-tab feature since f933dcbd (current HEAD 01825050) in /Users/ntwigg/projects/dormouse.workspace-tab. Stay on this branch/worktree. Review the entire feature diff, simplify where useful, preserve behavior, update owning specs for any changes, run relevant checks, and commit the result. Important invariants: tabs use shared Door geometry and active/inactive pane header colors, 220ms header palette tween, gradient clear of focus ring; command-mode arrows select workspace tabs/+ and Enter activates/creates and enters terminal immediately; clicking inactive tabs enters command mode, clicking active tabs renames without changing mode; title editing pauses ants and Enter resumes; focus animation retains the selected Door/tab origin; keyboard x reveals and confirms workspaces then focuses the next tab, Door deletion focuses the next minimized Door; closing the final workspace atomically creates a fresh workspace and selects its tab; create/select/close/transfer animate with pane 440ms timing; outgoing workspaces dim LINEARLY only to 50% opacity until incoming workspace expands, keep terminal elements attached through overlap and input exclusively on active Wall, support rapid reversals and reduced motion. Transfer of final workspace still closes its source Window, without creating a replacement. All 3244 lib tests and 42 transfer tests passed before the final fade tuning; final 32 motion/window tests, typecheck and spec lint passed. Do not push or open a PR; report your changes, validation, commit and any remaining concerns when finished.' --key enterResult, exit code 0:
sent surface:7 [2 inputs]
The sent prompt is 1600 ASCII bytes. Its expected beginning was /simplify and commit the complete workspace-tab feature since f933dcbd ....
Observed reception and submission
A subsequent dor read surface:7 --lines 45 showed the input still at the prompt, beginning with:
❯ th pane 440ms timing; outgoing workspaces dim LINEARLY only to 50% opacity until incoming workspace
expands, keep terminal elements attached through overlap and input exclusively on active Wall,
...
The assistant then sent a separate Enter:
dor send surface:7 --key enterResult: sent surface:7 [1 input]. The submitted message in the Claude transcript also begins th pane 440ms timing; ....
That starting text corresponds exactly to byte offset 1022 (zero-based) in the original ASCII prompt: the first 1022 bytes were absent, leaving its final 578 bytes, starting in the middle of with.
Claude's response, visible in the reporter's first screenshot:
The work is already committed and workspace-tab is clean at 0182505, so I re-ran validation and changed nothing. Your message arrived cut off at the start ("th pane 440ms timing..."), so I only have the second half of the brief. Nothing was pushed and no PR was opened.
This is evidence that the actual submitted prompt was truncated, not merely clipped in dor read or by the screenshot viewport. The lost prefix included /simplify, the feature baseline, repository scope, and much of the behavioral brief.
Successful retry
A shorter prompt was sent as text, followed by Enter in a separate tool call:
dor send surface:7 --raw --text '/simplify and commit the full workspace-tab feature diff f933dcbd..HEAD in this worktree. Preserve all existing behavior and regression tests, including the latest linear fade to 50% opacity. Read AGENTS.md and relevant specs. Review the entire feature, simplify where useful, test, and commit. Do not push or open a PR. Report completion, changes, tests, and commit.'
# Separate exec_command invocation:
dor send surface:7 --key enterThe retry is 367 ASCII bytes. Claude displayed its complete /simplify and commit ... f933dcbd..HEAD prefix, launched four simplify reviewers, and ultimately committed b73e1ace. The reporter's second screenshot shows the complete retry and the launched review agents.
The successful retry changed both prompt length and submission timing; it does not isolate which factor caused the first failure.
Expected / investigation boundary
- Deliver the entire text passed to
--text, in order, including the initial slash command. - Do not silently report successful delivery when the destination receives only a suffix.
- Inspect the
dor send→ host/PTY → interactive application input path, including initial-session readiness and the combined text/Enter sequence.
The cutoff offset is measured from the actual tool-call text and the observed received suffix. The responsible layer and reproducibility are still unknown. No controlled length sweep or additional reproduction was attempted, and no implementation changes were made for this report. The two user-provided screenshots are transcribed above; they are not uploaded as attachments here.
Activity
dormouse-bot commentedon Sep 17, 2026
No fix yet — I could not reproduce this in CI, and the reason is itself the useful finding: nothing between
dor sendand the PTY can produce this failure shape, so the discard almost certainly happens in the kernel tty layer, anddor sendis not the only caller exposed to it.The chain above the PTY is length-blind.
dor sendconcatenates--textand the--keybyte and hands over one string (encodeSendInputs). The control socket, the Rust bridge, and the sidecar all frame with newline-delimited JSON and reassemble from a buffer, so a framing fault would corrupt the JSON and fail the call, not yield well-formed ASCII. The host handler type-checksparams.inputand passes it through unsliced (use-dor-control.ts#L841). The cut also lands mid-word insidewithat a pure byte offset, which no JSON, UTF-8, or frame boundary in that chain can land on. I confirmed your arithmetic against the transcribed prompt: 1600 bytes, surviving text at offset 1022, 578 bytes delivered.What that leaves is the last hop, which has no flow control at all.
write()inpty-core.jsisp.write(data)— one unbounded burst into the pty master, no chunking and no pacing, and both hosts share it. A prefix vanishing while the suffix survives intact is the signature of an input-queue flush, not of per-byte drops (those would leave a hole in the middle, since the app is draining concurrently). 1022 sits just under BSD's 1024-byte tty input-queue bound, which is consistent with the flush happening once the queue crossed it.The wider consequence, and why this probably isn't a
dor sendbug. That same unguardedwrite()is the only path for clipboard paste too —writePasteToPtywrites the whole payload, bracketing markers included, in one call. Bracketed paste doesn't help here: the app is still reading the same tty in raw mode, so the same queue bound applies. If the mechanism is what the shape suggests, pasting more than ~1KB into any raw-mode TUI on macOS loses its head just as silently, and the durable fix is chunk-and-pace inside that one sharedwrite()rather than anything indor. Thesentresponse is unconditional by construction — the handler responds right afterwritePtyand there is no acknowledgement channel from the PTY — so no amount of care indorcan make the success report honest; bounding the burst is what would.One run settles it. This isolates the kernel from everything Dormouse does — no node-pty, no sidecar, no webview. On this Linux runner it shows no loss at any size (the master write goes short at 16KB and libuv retries it, which is the correct behavior); if
starts_atcomes back non-zero on your Mac, the tty layer is confirmed and the fix location is settled.tty-input-limit.py — run on the affected Mac
Linux runner, for comparison —
starts_atis 0 at every size, meaning nothing was dropped:reader_delaystands in for an app that isn't draining instantly. Raising it widens the window; a reader that drains fast enough may mask the loss, which also explains why the 367-byte retry succeeded without isolating length from timing.I stopped short of a fix deliberately: choosing a chunk size and pacing without a measured threshold is guesswork, and a wrong bound would look like it worked while still losing data on a slower reader.