Skip to content

fix(agents): guard Windows CLI argv against ENAMETOOLONG by moving system prompt to stdin#69211

Merged
obviyus merged 1 commit intoopenclaw:mainfrom
skylee-01:fix/issue-69158
Apr 25, 2026
Merged

fix(agents): guard Windows CLI argv against ENAMETOOLONG by moving system prompt to stdin#69211
obviyus merged 1 commit intoopenclaw:mainfrom
skylee-01:fix/issue-69158

Conversation

@skylee-01
Copy link
Copy Markdown
Contributor

Summary

  • Problem: On Windows, the claude-cli provider passes the system prompt (~10K-30K+ chars) via --append-system-prompt as a CLI argument. Windows CreateProcessW has a ~32,767-char command-line limit, causing spawn ENAMETOOLONG on every message.
  • Why it matters: 100% failure rate for all Windows users using the claude-cli provider. The agent cannot send any messages.
  • What changed: Added an argv length guard that detects when the assembled command line exceeds the Windows limit and moves the system prompt from CLI args into stdin.
  • What did NOT change (scope boundary): Non-Windows platforms are unaffected (guard returns null). Backends that use systemPromptFileConfigKey (e.g., Codex) are unaffected. Short command lines on Windows are unaffected.

Change Type (select all)

  • Bug fix

Scope (select all touched areas)

  • Gateway / orchestration

Linked Issue/PR

Root Cause (if applicable)

  • Root cause: The claude-cli backend config uses systemPromptArg: "--append-system-prompt" to pass the full system prompt as a CLI argument. On Windows, Node.js spawn (without shell: true) still assembles all argv entries into a single command-line string for CreateProcessW, which has a ~32,767-char limit. System prompts routinely exceed 10K chars, pushing the total well past the limit.
  • Missing detection / guardrail: No pre-spawn check existed for total argv length on Windows. The systemPromptFileConfigKey mechanism (used by Codex) would avoid this, but claude-cli doesn't use it.
  • Contributing context (if known): The issue manifests as UV_ENAMETOOLONG from libuv, which Node.js surfaces as spawn ENAMETOOLONG. This affects all Windows users using claude-cli as a provider backend.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
  • Target test or file: src/agents/cli-runner.helpers.test.ts
  • Scenario the test should lock in: When running on Windows (platform: "win32") and the assembled argv exceeds 30,000 chars (the safe threshold), the system prompt is stripped from args and prepended to stdin.
  • Why this is the smallest reliable guardrail: The guard logic is a pure function with injectable platform parameter, testable without spawning real processes or requiring a Windows environment.
  • Existing test that already covers this (if any): None
  • If no new test is added, why not: N/A (11 new tests added across 3 describe blocks)

User-visible / Behavior Changes

Windows users using the claude-cli provider will no longer see spawn ENAMETOOLONG errors. The system prompt is moved from a dedicated CLI flag to the stdin payload as a prefix, which is a semantic compromise (the model sees it as part of the user message rather than a system prompt) but is strictly better than a 100% crash rate.

Diagram (if applicable)

Before (Windows, long system prompt):
[buildCliArgs] -> [--append-system-prompt <30K chars>] -> [spawn] -> ENAMETOOLONG crash

After (Windows, long system prompt):
[buildCliArgs] -> [--append-system-prompt <30K chars>]
    -> [applyWindowsArgvGuard] -> strips system prompt from args
    -> [spawn] -> stdin = "<system prompt>\n\n<user message>" -> success

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Repro + Verification

Environment

  • OS: Windows (any version)
  • Runtime/container: Node 22+
  • Model/provider: claude-cli provider backend
  • Integration/channel (if any): Any channel using claude-cli
  • Relevant config (redacted): Backend with systemPromptArg: "--append-system-prompt" and system prompt > ~10K chars

Steps

  1. Configure OpenClaw on Windows with claude-cli as a provider backend
  2. Send any message to the agent
  3. The CLI runner assembles --append-system-prompt <full system prompt> into argv
  4. CreateProcessW rejects the command line exceeding 32,767 chars

Expected

  • Message is processed and a response is returned

Actual

  • spawn ENAMETOOLONG error on every message attempt

Evidence

Attach at least one:

  • Failing test/log before + passing after

All 29 tests in src/agents/cli-runner.helpers.test.ts pass, including 11 new tests:

  • estimateArgvByteLength: accounts for quoting overhead (2 tests)
  • stripSystemPromptFromArgv: removes flag+value, returns null for absent/undefined/trailing flag (4 tests)
  • applyWindowsArgvGuard: returns null on non-Windows/short-args/missing-flag, moves system prompt to stdin on Windows with long args, handles empty stdin (5 tests)
 Test Files  1 passed (1)
      Tests  29 passed (29)

Human Verification (required)

  • Verified scenarios: All new guard functions tested with injectable platform parameter simulating Windows and non-Windows. Verified integration point in execute.ts where the guard is applied between buildCliArgs and supervisor.spawn.
  • Edge cases checked: Empty stdin payload, missing system prompt arg, system prompt flag as last argv entry (no value), non-Windows platforms, short argv that fits within limits.
  • What you did not verify: Did not test on actual Windows hardware. Did not test with a real Claude CLI binary. The fix is structural and testable via unit tests with the injectable platform parameter.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No

Risks and Mitigations

  • Risk: On Windows when the guard activates, the system prompt becomes part of the user message (stdin) rather than a dedicated system prompt. The model may treat it differently.
    • Mitigation: This only activates when the alternative is a 100% crash. The system prompt content is preserved verbatim as a prefix to stdin with a double-newline separator. For backends that need strict system prompt separation, they can adopt systemPromptFileConfigKey (the file-based approach used by Codex).

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: M labels Apr 20, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 20, 2026

Greptile Summary

This PR adds a Windows command-line length guard to the CLI runner that detects when a long system prompt would push CreateProcessW past its ~32 K char limit and transparently moves the system prompt from argv into stdin as a prefix. The fix is scoped correctly — non-Windows platforms and backends using systemPromptFileConfigKey are unaffected — and the 11 new unit tests cover all meaningful edge cases with an injectable platform parameter.

Confidence Score: 5/5

Safe to merge; fixes a 100% Windows crash with correct scoping and good test coverage. The one comment is a minor observability improvement, not a blocker.

All findings are P2 (style/observability). The core logic is correct, edge cases are handled, and the integration point is well-placed. No data loss, security, or correctness issues were found.

No files require special attention.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/agents/cli-runner/helpers.ts
Line: 497-498

Comment:
**Silent no-op when argv is still too long after stripping**

If `stripped` is `null` (the system prompt flag isn't present, or it's the last token with no value), the guard returns `null` and the spawn will still hit `ENAMETOOLONG` on Windows. That failure path already existed, but now it's harder to distinguish from "guard didn't trigger" vs "guard tried and couldn't help." A debug/warn log before returning `null` here would make diagnosing lingering issues much easier.

(Consider adding a `cliBackendLog.warn(...)` before the `return null` noting that the argv limit is exceeded but the system prompt flag couldn't be found.)

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(agents): guard Windows CLI argv agai..." | Re-trigger Greptile

Comment thread src/agents/cli-runner/helpers.ts Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 18a07cd4f4

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/cli-runner/execute.ts Outdated
@Stache73
Copy link
Copy Markdown

Just confirmed a clean repro of the underlying issue on OpenClaw 2026.4.22 and posted details on #69158. Summary here in case it's useful to you:

  • Platform: Windows 11, Node v24.6.0, OpenClaw 2026.4.22 (00bd2cf). claude-cli backend via OAuth.
  • Trigger: openclaw infer model run --model claude-cli/claude-opus-4-7 --prompt "<31 chars>" — failed with spawn ENAMETOOLONG on every fallback in the chain (opus-4.7 → sonnet-4.6 → opus-4.6 → opus-4.5 → sonnet-4.5 → haiku-4.5). Workspace path is short.
  • Bootstrap context in my install: Total bootstrap injected chars: 51,507 (86% of max/total 60,000), individual files at 89–98% of their 12K caps. So the argv guard is exactly the right place to intervene — the user prompt was 30 chars, the system prompt is where the 30K+ lives.

On the approach in this PR

The trade-off you flagged (system prompt becomes a stdin prefix rather than a dedicated --append-system-prompt flag when the guard fires) is probably acceptable in practice, but worth calling out one subtle effect for Claude specifically:

Claude Code CLI's --append-system-prompt layers on top of its own built-in system prompt rather than replacing it. When the system prompt is folded into stdin as a prefix, it becomes part of the user-turn content instead of appended to system. In most cases the model behavior will be very close, but for prompts that rely on tool-invocation patterns, role-boundary heuristics, or safety framing to be at the system layer, the stdin-prefix path could produce subtly different generations. Since the guard only fires when the alternative is a 100% crash, "slightly different but coherent output" is strictly better — just a documentation note for users might help: "on Windows installs with large bootstrap files, Claude is invoked in stdin-prefix mode" or similar, so people don't chase ghost-differences in output quality.

Maybe also worth considering (not a blocker): systemPromptFileConfigKey — the same mechanism Codex uses — would pass the system prompt via a temp-file path and avoid the argv-length problem entirely without changing the semantic layer. Possibly a follow-up if Claude CLI grows support for a file-based flag; from a quick read Claude Code currently has --append-system-prompt as a string only, so stdin-prefix is the correct tactic today.

Related filings

Thanks for the clean guard + test coverage. Happy to be a Windows test target once this lands.

Copy link
Copy Markdown
Contributor

@obviyus obviyus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified the Windows argv failure path: a 40k system prompt passed as argv reproduces spawn ENAMETOOLONG, while Claude's prompt-file flag starts cleanly and preserves stdin.

Maintainer follow-up: rebased, replaced the stdin-prefix workaround with --append-system-prompt-file, updated tests/config docs/changelog, and confirmed prior review threads are resolved/outdated.

Local gate: pnpm test src/agents/cli-runner.helpers.test.ts src/agents/cli-runner.spawn.test.ts src/agents/cli-backends.test.ts, pnpm config:docs:check, pnpm config:schema:check, plus Windows spawn smoke.

@obviyus obviyus merged commit f7b71ab into openclaw:main Apr 25, 2026
53 of 58 checks passed
@obviyus
Copy link
Copy Markdown
Contributor

obviyus commented Apr 25, 2026

Landed on main.

Thanks @skylee-01.

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

Labels

agents Agent runtime and tooling docs Improvements or additions to documentation extensions: anthropic gateway Gateway runtime size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: spawn ENAMETOOLONG on Windows when using claude-cli provider

3 participants