Skip to content

fix(server): agents read RTF paragraphs that end with a backslash - #30848

Open
geezoria wants to merge 1 commit into
pingdotgg:mainfrom
geezoria:fix/agent-rtf-text-newline-paragraphs
Open

geezoria wants to merge 1 commit into
pingdotgg:mainfrom
geezoria:fix/agent-rtf-text-newline-paragraphs

Conversation

@geezoria

@geezoria geezoria commented Nov 4, 2026

Copy link
Copy Markdown

Problem

When a user attaches an .rtf file, the server extracts its text so the agent can read it. A document saved by macOS TextEdit reaches the agent as a single run-on paragraph.

The RTF spec treats a backslash followed by a carriage return or line feed as \par, and TextEdit ends every paragraph that way:

\f0\fs24 \cf0 First\
Second\
Third}

The rtf() reader in documentTextWorker.ts skipped the pair as an unknown control symbol, so the agent received FirstSecondThird. An explicit \par in the same position already yields a newline. The agent loses the paragraph, list and line structure of the document it was asked to read.

Change

In the reader's control-symbol branch, a backslash before \n or \r now yields the same "\n" that \par yields, and respects hidden destinations in the same way. For \<CR><LF>, the \r is consumed with the backslash, and the loop already skips the bare \n that follows, so CRLF files give one newline, not two.

Scope and approval

This is a very small, focused fix for an obvious bug, so it uses CONTRIBUTING's exception for those. It is a one-line parsing correction for a form the RTF spec defines and macOS TextEdit writes. Documents that use \par are unchanged.

It is the agent-facing counterpart of #30812, which fixes the same rule in the web file preview's separate RTF parser. The two readers share no code and either fix stands alone, so they are separate PRs as CONTRIBUTING asks.

I found no other existing issue or PR. I searched open and closed PRs and issues for rtf, rtf paragraph, textedit, and document text rtf. The reader came from #21458, and #29796 / #29827 changed other formats. #29760 doesn't touch this file.

Verification

Windows 11, Node 26.12.1, base 8b44f42b14.

  • Regression test. Added a line break paragraphs row, covering LF and CRLF, to the existing extracts RTF symbols and Unicode fallbacks table in userInputAttachments.test.ts. With main's reader it fails, receiving FirstSecondThird. With this change it passes, along with every other RTF row. The table also checks the cached .txt the agent is pointed at.
  • Fast checks. vp run check:changed origin/main passed format, lint, both Knip checks, GraphQL, and typechecks for 3 affected packages. Its test step ran 32 tests: 29 passed and 3 failed. The 3 failures are other cases in userInputAttachments.test.ts (the notebook, PDF/binary, and "readable server path" cases) that assert POSIX-style paths. They fail identically on clean main on this Windows machine and don't involve RTF. Linux CI should run them normally.

Not checked: I'm on Windows, so I didn't attach a file produced by TextEdit itself. The test input reproduces TextEdit's body structure. I also didn't start a provider turn that reads the attachment. The change is confined to the extractor the attachment path calls.

Implemented and verified with Claude Opus 5.5 (claude-opus-5-5) in the Claude Code harness.

🤖 Generated with Claude Code

RTF treats a backslash followed by a carriage return or line feed as \par,
and macOS TextEdit ends every paragraph that way. The document text extractor
skipped the pair, so an attached TextEdit file reached the agent as one
run-on paragraph.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XS 0-9 changed lines (additions + deletions). labels Nov 4, 2026
@macroscopeapp

macroscopeapp Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at b474297

Macroscope's review found this PR approvable — This preserves paragraph breaks when reading RTF documents whose paragraphs end with a backslash and line break. The change is confined to text extraction and retains existing parser safeguards.

You can add or adjust custom eligibility rules. Learn more.

@coderabbitai

coderabbitai Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

Explain Change →

No actionable comments were generated in the recent review.

Walkthrough

The server RTF parser now emits a newline for backslash-LF and backslash-CR when the current state is not skipped. An extraction test covers backslash-LF and backslash-CRLF input.

Changes: RTF paragraph breaks

Layer / File(s) Summary
Parser handling and extraction test
apps/server/src/provider/documentTextWorker.ts, apps/server/src/provider/userInputAttachments.test.ts
The parser emits a newline for backslash followed by LF or CR when the current state is not skipped. The test checks extraction of backslash-LF and backslash-CRLF as newline-separated text.

Estimated code review effort: 2 (Simple) | ~5 minutes

Change: Bug fix

Possibly related PRs

Suggested labels: bugfix, size:XS

Merge Risk: Minimal · up to b4742

The narrowly scoped RTF fix has focused LF and CRLF coverage, with no actionable merge-blocking risk identified.

Pre-merge checks: Passed 4
Check name Status Explanation
Title check Passed The title names the server-side fix: agents now read RTF paragraphs that end with a backslash. The raw summary and objectives confirm the reader now emits a newline when a backslash is followed by LF …
Description check Passed The description follows the template's structure: Problem, Change, Scope and approval, and Verification. The Problem section gives the expected behavior, the reproduction input, and the observed run-o…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title names the server-side fix: agents now read RTF paragraphs that end with a backslash. The raw summary and objectives confirm the reader now emits a newline when a backslash is followed by LF or CR, matching the main change. The title is specific and conventional, and it follows the repository's conventional-commit example.

Full details: Description check

Explanation

The description follows the template's structure: Problem, Change, Scope and approval, and Verification. The Problem section gives the expected behavior, the reproduction input, and the observed run-on output. The Change section explains the control-symbol branch update and the CRLF handling. The Scope section explains why the small-fix exception applies and links the related PR #30812. The Verification section gives the regression test, the environment, the results, and the 3 failing unrelated cases on Windows. It also states what was not checked. The template's optional agent-attribution line is present. The description is complete and specific.

Finishing Touches
Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 8b44f42 and b474297.

Files selected for processing: 2
  • apps/server/src/provider/documentTextWorker.ts
  • apps/server/src/provider/userInputAttachments.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 6 remain after this review.

@maria-rcks

maria-rcks commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

Note

Written by gpt-6.1-sol on behalf of Maria

Verified in a real dev environment

verified unchanged head b474297b688f4f1e20fc084852eb91b1baba02d8 on a linux boat vm. reproduced on github/main at 9e85538535 in a separate worktree first. ran vp i and background vp run dev for each checkout, stopping main before starting the pr.

used the repo's playwright with /usr/bin/google-chrome. added a real git repository through the web ui, created threads, attached five rtf documents, and submitted real codex gpt-6.1-sol turns. codex read the server-provided extracted .rtf.txt paths with a shell command. checked the provider protocol logs, tool output, server-generated files, and rendered response. no mocks or replay provider.

  • main: backslash-lf, backslash-crlf, and backslash-cr each reached codex as FirstSecondThird (one line).
  • pr: all three reached codex as First\nSecond\nThird (three lines). crlf produced exactly one newline per paragraph.
  • nearby behavior: explicit \par still produced three lines. unicode and tabs remained Visible\nα\tEnd\nLast; the hidden destination and its line break stayed excluded.
  • blacksmith: vp test run apps/server/src/provider/userInputAttachments.test.ts passed all 31 tests. current ci checks passed. no source changes or pushes were needed.

actual provider command transcript and output

before, main with the same five attachments:

main: codex receives collapsed lf, crlf, and cr paragraphs

after, exact pr head with the same five attachments:

pr: codex receives three lines and neighboring extraction remains correct

Not verified

native ios/android and electron clients, other providers, and macos/windows execution were not exercised on this linux vm. the rtf documents were hand-authored with the documented textedit paragraph encoding; no file was saved by textedit itself. the separate web file-preview parser was outside this server-extraction verification. uploaded video playback and seeking were checked in t3's browser.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XS 0-9 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants