Workflow({scriptPath}) false-fails "hidden control characters" on a clean file with CRLF line endings
Workflow({scriptPath}) false-fails "hidden control characters" on a clean file with CRLF line endings
Summary
Calling the Workflow tool with {scriptPath, args} fails with a permission-validation error on a script file that contains no suspicious content — only normal Windows CRLF (\r\n) line endings. The identical script content passed inline via {script, args} works fine.
Error
The permission handler returned updatedInput for Workflow that failed schema validation: [{"code":"custom","path":["script"],"message":"script contains control characters that would be hidden in the approval dialog"}]
Repro
- On a Windows checkout with
core.autocrlf=true(the Windows git default), check out a repo containing a.claude/workflows/*.mjsscript that is committed as LF-only. Git will check it out locally as CRLF. - Verify the file has no actually-hidden control characters: no
\x00-\x1Fbesides the CRLF pairs themselves, no C1 controls, no zero-width/bidi Unicode, no lone\r(i.e. every\ris immediately followed by\n). - Call
Workflow({ scriptPath: '.claude/workflows/<file>.mjs', args: {...} }). - Observe the schema-validation error above, blocking the call entirely — no approval dialog is even shown.
- Call
Workflow({ script: <same file's contents as a string>, args: {...} })instead — this succeeds. (Note: reading the file with a normal file-read tool and passing its string content this way naturally normalizes to\n-only, since the tool-call JSON payload itself has no raw\rbytes.)
This reproduced identically across two separate attempts with different args payloads (one containing escaped \n and Unicode bullets/em-dashes in prompt strings, one plain-ASCII) — ruling out args-content as the trigger. It is specific to the scriptPath → script resolution path.
Expected
A file whose only "control characters" are \r bytes that are each immediately followed by \n (i.e. valid CRLF line endings — completely normal on any Windows checkout, not a hidden/invisible character in any editor or terminal) should not trip a "hidden control characters" safety check. Either:
- normalize CRLF → LF (or otherwise treat
\r\nas equivalent to\n) before running the hidden-character scan, matching how the inlinescript:path behaves, or - exclude
\rimmediately followed by\nfrom the flagged character set, only flagging genuinely anomalous control characters (lone\r, C0/C1 controls other than tab/newline, zero-width/bidi Unicode, etc).
Impact
This defeats the specific convenience the scriptPath mode advertises — iterating on a saved workflow script on disk without resending the full script text on every call — for any repo checked out on Windows with default core.autocrlf settings (extremely common). The only workaround is to always read the file and pass its content inline via script:, which reintroduces the token cost scriptPath exists to avoid.
Environment
- OS: Windows 11
- Git:
core.autocrlf=true(default on Windows) - Workflow tool available via Claude Code / Agent SDK
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗