[BUG] Windows npm claude.cmd returns raw prose for a complex --json-schema call while claude.exe succeeds
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
On Windows, invoking Claude Code through the npm-installed claude.cmd shim
can cause a structured-output invocation to return raw Markdown prose instead
of the JSON result envelope.
Running the same prompt, schema, image, working directory, and flags against
the underlying claude.exe returns a valid envelope containingstructured_output.
The behavior was deterministic in my original reproduction:
| Launcher | Result |
|---|---|
| claude.cmd | Raw prose / non-JSON envelope, 3 out of 3 trials |
| claude.exe | Valid structured_output, 3 out of 3 trials |
The failing subprocess exits with code 0. Callers therefore detect the
contract violation only when they attempt to parse stdout.
What Should Happen?
The npm-installed claude.cmd launcher should preserve the argument boundaries
of a complex multiline -p prompt and produce the same result as invoking the
underlying claude.exe directly.
When both --output-format json and --json-schema are supplied, stdout should
be a JSON result envelope containing a schema-valid structured_output field.
Error Messages/Logs
Claude Code does not emit an explicit CLI error. The claude.cmd process exits
with code 0, but stdout is not valid JSON.
Application-side parsing error:
ProviderExecutionError: claude-cli structured-output mode returned a non-JSON envelope
Raw stdout begins with explanatory Markdown instead of a JSON result envelope:
I've read the contact sheet. Here's what's on it:
[document-specific output redacted]
Expected stdout shape (payload values redacted):
{
"type": "result",
"subtype": "success",
"is_error": false,
"structured_output": {
"boundaries": "[REDACTED]",
"continuations": "[REDACTED]",
"needs_full_page": "[REDACTED]"
}
}
Observed A/B result:
claude.cmd
trial 1/3: non-JSON envelope
trial 2/3: non-JSON envelope
trial 3/3: non-JSON envelope
claude.exe
trial 1/3: structured output returned
trial 2/3: structured output returned
trial 3/3: structured output returned
The claude.exe output was successfully parsed as a JSON result envelope, andstructured_output contained a schema-compliant object.
Steps to Reproduce
Save a non-sensitive PNG as synthetic-contact-sheet.png, then run the script
below from the directory containing that image. The script mirrors the original
invocation shape, including the Read tool, image-scoped working directory,
closed stdin, structured-output flags, and multiline prompt.
The original A/B comparison used the same private source image for both
launchers. That image cannot be attached, so document-specific values in this
report are redacted.
import json
import subprocess
from pathlib import Path
CLAUDE_CMD = Path(
r"C:\Users\<USER>\AppData\Roaming\npm\claude.cmd"
)
CLAUDE_EXE = Path(
r"C:\Users\<USER>\AppData\Roaming\npm\node_modules"
r"\@anthropic-ai\claude-code\bin\claude.exe"
)
IMAGE = Path("synthetic-contact-sheet.png").resolve()
BASE_PROMPT = """This image is a contact sheet: 16 cells in a 4x4 grid, read
left to right then top to bottom. Each cell shows the top portion of one page.
The red number in each cell's top-left corner is the page number.
Identify which pages START a new document. If a page cannot be classified from
the visible portion, put it in needs_full_page rather than guessing.
Reply with ONLY one JSON object:
{"boundaries": [{"page": 1, "type_guess": "other",
"type_label": "document title", "confidence": 0.9,
"evidence": "what was visible"}],
"continuations": [2, 3],
"needs_full_page": []}
"""
PROMPT = (
f"Read the image file at {IMAGE.name} and then:\n\n"
+ BASE_PROMPT
)
SCHEMA = {
"type": "object",
"properties": {
"boundaries": {
"type": "array",
"items": {
"type": "object",
"properties": {
"page": {"type": "integer"},
"type_guess": {"type": ["string", "null"]},
"type_label": {"type": ["string", "null"]},
"confidence": {"type": "number"},
"evidence": {"type": "string"},
},
"required": [
"page",
"type_guess",
"type_label",
"confidence",
"evidence",
],
},
},
"continuations": {
"type": "array",
"items": {"type": "integer"},
},
"needs_full_page": {
"type": "array",
"items": {"type": "integer"},
},
},
"required": [
"boundaries",
"continuations",
"needs_full_page",
],
}
def run(name, launcher):
args = [
str(launcher),
"-p",
PROMPT,
"--safe-mode",
"--output-format",
"json",
"--json-schema",
json.dumps(SCHEMA, separators=(",", ":")),
"--allowedTools",
"Read",
]
result = subprocess.run(
args,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
stdin=subprocess.DEVNULL,
cwd=IMAGE.parent,
)
print(f"\n===== {name} =====")
print("return code:", result.returncode)
try:
envelope = json.loads(result.stdout)
except json.JSONDecodeError:
print("VERDICT: NON-JSON OUTPUT")
print("stdout prefix:", result.stdout[:500])
print("stderr prefix:", result.stderr[:500])
return
structured = envelope.get("structured_output")
if isinstance(structured, dict):
print("VERDICT: STRUCTURED OUTPUT OK")
print("structured_output keys:", sorted(structured))
else:
print("VERDICT: JSON ENVELOPE WITHOUT STRUCTURED_OUTPUT")
print("stdout prefix:", result.stdout[:500])
if __name__ == "__main__":
assert IMAGE.is_file(), IMAGE
assert CLAUDE_CMD.is_file(), CLAUDE_CMD
assert CLAUDE_EXE.is_file(), CLAUDE_EXE
run("claude.cmd", CLAUDE_CMD)
run("claude.exe", CLAUDE_EXE)
Actual Behavior
Through claude.cmd
The command returns exit code 0, but stdout begins with explanatory Markdown:
I've read the contact sheet. Here's what's on it:
...
There is no parseable JSON result envelope and no structured_output field.
Through claude.exe
The same prompt, schema, image, working directory, and flags produce a valid
JSON result envelope containing a schema-compliant structured_output object.
Environment
- Claude Code version:
2.1.220 - Operating system: Windows 11
- Launcher installed through npm
- Parent shell: PowerShell / Git Bash
- Child process launched from Python with
subprocess.run([...]) - Authentication/provider: first-party Claude Code
- Model: CLI default; no
--modelargument was supplied
The generated claude.cmd shim contains:
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
"%dp0%\node_modules\@anthropic-ai\claude-code\bin\claude.exe" %*
The %* forwarding appears to be the relevant difference. Complex multiline
prompt arguments containing embedded double quotes may be reparsed according
to cmd.exe rules before reaching claude.exe.
Workaround
Configure integrations to invoke the underlying executable directly:
C:\Users\<USER>\AppData\Roaming\npm\node_modules\@anthropic-ai\claude-code\bin\claude.exe
Related Issues
Possibly related to #18536, but that report received a JSON envelope withoutstructured_output. In this reproduction, claude.cmd emits raw prose while
the underlying claude.exe succeeds with the same arguments.
Claude Model
CLI default; no --model argument was supplied.
Is this a regression?
I don't know.
Last Working Version
No response.
Claude Code Version
2.1.220
Platform
Anthropic API
Operating System
Windows
Terminal/Shell
PowerShell
Additional Information
The issue reproduces only when the invocation goes through the npm-installed
Windows claude.cmd shim. Invoking the bundled native claude.exe directly is
the current workaround.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗