Skip to content

safeoutputs JSON stdin mode reports invalid JSON as "No arguments provided" and prints help #52848

Description

@crmitchelmore

Summary

When an agent uses the documented JSON-payload form —

safeoutputs <tool> . < payload.json
printf '{...}' | safeoutputs <tool> .

— and the payload is not valid JSON, mcp_cli_bridge.cjs silently discards the parse
error and falls through to flag parsing. Flag parsing finds no flags, so the bridge logs:

[warning] [safeoutputs] No arguments provided for '<tool>'; showing command help instead of calling the tool

and prints the command help.

The message is inaccurate in a way that actively misleads. Arguments were provided and
stdin was read; the payload simply failed JSON.parse. An agent reading that output
concludes the CLI does not implement stdin at all — which is what happened to us, twice,
including one agent that filed a bug against its own tooling saying exactly that.

Why this matters more for agents than for humans

A human sees "no arguments provided", looks at their command line, and spots the typo. An
agent sees it, believes the documented interface is unavailable, and abandons the work it
has already done. In our case the payload was a code-review verdict assembled by the model:
Markdown prose containing backticks, quotes and backslashes, embedded into a single-line
JSON string. That is exactly the input most likely to produce invalid JSON, and the least
likely to be diagnosed from a help screen.

Measured in one repository over two weeks: 92 failed runs, each discarding a completed
review. Three sampled payloads, all invalid, all reported as "no arguments provided":

2871 bytes -> Expecting ',' delimiter: line 1 column 2159
6464 bytes -> Invalid \escape:         line 1 column 5801
6121 bytes -> Expecting ',' delimiter: line 1 column 1399

The stdin path itself is fine

Worth stating plainly, since it took us a while to establish: JSON mode works. A run in
the same workflow, same version, same invocation shape, with valid JSON on stdin was
accepted and emitted normally. --body . per-field stdin also works and read 5678 bytes in
another run. There is no bug in readStdinSync() or in hasStdinJsonPayload() — the only
defect is the swallowed exception and the misleading message that follows it.

Location

actions/setup/js/mcp_cli_bridge.cjs, in parseToolArgs (v0.86.2, around lines 770–785):

if (trimmedStdin) {
  try {
    const parsed = JSON.parse(trimmedStdin);
    ...
  } catch {
    // Not valid JSON; fall through to normal flag-based argument parsing.
  }
}

The fall-through is reasonable when args contains real flags. It is not reasonable when
args is exactly ['.'], because . is unambiguously a request for JSON-payload mode —
there is nothing to fall through to, and the resulting help screen describes a failure
that did not occur.

Reproduction

printf '{"body":"a "quote" breaks it"}' | safeoutputs add_comment .
# observed: "No arguments provided for 'add_comment'; showing command help"
# expected: a JSON parse error naming the offending position

Suggested fix

When args is ['.'] (or empty with piped stdin) and JSON.parse throws, fail with the
parse error rather than falling through:

Error: stdin is not valid JSON: Invalid \escape at line 1 column 5801.
JSON payload mode was requested with '.'. Pass --key value flags instead, or correct the JSON.

Two smaller things would each have saved us independently:

  1. Bridge invoked: argumentCount=N counts argv only, so a JSON-mode call always logs
    argumentCount=1. Logging whether stdin was read, and how many bytes, would make the
    distinction visible in run logs.
  2. The "no arguments provided" wording could be reserved for the case where genuinely
    nothing was supplied.

Priority

Low urgency for us — we have routed around it by having agents pass Markdown through
--body . and never hand-assemble JSON, which is more robust anyway. Raising it because
the diagnostic sends agents to a confident wrong conclusion, and because the generated
prompt preamble recommends the JSON form to every agent:

For multiple or complex arguments, pipe a JSON object on stdin using `.` as the sentinel:
printf '{"item_number":42,"body":"### Title\n\nBody."}' | safeoutputs add_comment .

Version: v0.86.2. Happy to open a PR against parseToolArgs if the suggested direction
looks right.

Activity

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

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions