[BUG] Workflow by-name launch fails on Windows: resolved script is rewritten to CRLF and rejected by the approval gate

Open 💬 1 comment Opened Jun 26, 2026 by oviano

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

  1. On Windows, create an LF-only workflow script at .claude/workflows/le-probe.js (verify zero \r bytes), e.g.:

``js
export const meta = {
name: 'le-probe',
description: 'line-ending probe',
phases: [{ title: 'Probe' }],
}
phase('Probe')
log('ran')
``

  1. Invoke it by name: Workflow({ name: "le-probe" }) (or a registered/built-in name resolving from .claude/workflows/).
  2. 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.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗