[BUG] Workflow by-name launch fails on Windows: resolved script is rewritten to CRLF and rejected by the approval gate
Summary
On Windows, launching a Workflow by name (Workflow({name: "..."}), resolving a script from .claude/workflows/) fails at the approval gate with a control-character validation error -- even when the installed script file is pure LF on disk. Invoking the same file by scriptPath succeeds. The by-name resolution path appears to re-persist the resolved script with CRLF line endings before the approval gate inspects it, so the gate rejects its own re-written copy.
Environment
- Claude Code: 2.1.193
- OS: Windows 11 (10.0.26200)
- Shell: Git Bash + PowerShell
Steps to reproduce
- On Windows, create an LF-only workflow script at
.claude/workflows/le-probe.js(verify zero\rbytes), e.g.:
``js``
export const meta = {
name: 'le-probe',
description: 'line-ending probe',
phases: [{ title: 'Probe' }],
}
phase('Probe')
log('ran')
- Invoke it by name:
Workflow({ name: "le-probe" })(or a registered/built-in name resolving from.claude/workflows/). - Invoke the same file by path:
Workflow({ scriptPath: ".claude/workflows/le-probe.js" }).
Expected
Both forms launch the workflow; an LF script with no carriage returns passes the approval gate.
Actual
- By name -> rejected at the permission gate:
````
script contains control characters that would be hidden in the approval dialog
(validation path: ["script"])
- By scriptPath -> launches and runs normally.
The only difference is the invocation form; the on-disk file is byte-identical and LF-only in both cases.
Diagnosis
The approval gate correctly rejects carriage returns (0x0D) as control characters that could hide content in the approval dialog -- that part is working as intended. The problem is upstream of the gate: the by-name resolver persists the resolved script to a session file, and on Windows that write uses text mode (translating \n->\r\n), so the script handed to the gate contains CRLF regardless of the source file's endings. scriptPath reuses the on-disk file directly (the launch result reports Script file: as the original path), so it never hits the re-write.
This is the same class of CRLF-on-Windows write issue documented in #19565 (Edit/Write) and #38887 (general CRLF handling), surfacing here in the Workflow name-resolution path.
Impact
Narrow but total where it lands: any project that ships named workflow scripts under .claude/workflows/ and runs them on Windows cannot launch them by name at all. scriptPath is a working workaround, but it bypasses the name registry.
Suggested fix
Persist the resolved by-name script with LF endings (write bytes, or newline=""/newline="\n"), so a clean LF workflow survives to the approval dialog intact.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗