fix: inline --json-schema file paths so structured output works on CLI 2.1.160 (#3) #4

Merged
ryan merged 1 commits from fix/json-schema-inline-file into gitea 2026-06-03 02:52:33 +02:00
Owner

Fixes #3.

Problem

The README, examples/gitea-structured-output.yml, and the claude_args
input description all present the file-path form
--json-schema /path/to/schema.json as the primary way to get
schema-validated output. But the CLI this action installs by default
(2.1.160) only accepts an inline JSON schema string — a file path makes
the CLI exit 0 with empty output. The action then finds no structured_output
and hits its fail-loud branch, so a consumer who follows the documented recipe
gets a step that fails closed on every run, with no useful diagnostic.

Fix

This implements the issue's proposed solution #2 (auto-inline the file),
plus the doc clarification from #1. I chose #2 because it dominates the
alternatives:

  • Doc-only (#1): would push everyone to the inline single-quoted form,
    reintroducing the exact shell $-expansion hazard the file-path form exists
    to avoid for schemas with $ref/$defs.
  • Bump the default CLI (#3): unverified, and changes the default for every
    consumer.

prepareRunConfig now resolves each --json-schema value (both
--json-schema <v> and --json-schema=<v>) before spawning the CLI:

  • A value that's already inline JSON (trimmed, starts with {/[) is left
    exactly as supplied.
  • A file path is read in-process, validated, and passed inline as a
    compact JSON string. Because the value reaches the CLI as an argv element
    (never through a shell), a schema containing $ref/$defs round-trips
    intact — and it works on any CLI, including the default 2.1.160.
  • An unreadable file or invalid JSON now fails with a clear, specific error
    instead of the CLI's silent empty output.

Docs (README structured-output notes, the example workflow, and the
action.yml claude_args description) are corrected to explain the file-path
form is auto-inlined and works on the default CLI. The narrowed CLI-version
caveat is kept (a pinned/pre-baked CLI must still support the --json-schema
flag itself).

Tests

Added an inlineJsonSchemaArgs suite (real temp files): inline passthrough,
file-path inlining for the space and equals forms, the $ref/$defs from
file round-trips intact
case, missing-file and invalid-JSON error paths, plus
an end-to-end prepareRunConfig test. bun test on run-claude.test.ts:
45 pass / 0 fail. tsc --noEmit clean (base-action + root).

If you'd actually prefer solution #1 or #3 instead, happy to redirect.

Fixes #3. ## Problem The README, `examples/gitea-structured-output.yml`, and the `claude_args` input description all present the **file-path** form `--json-schema /path/to/schema.json` as the primary way to get schema-validated output. But the CLI this action installs **by default** (2.1.160) only accepts an **inline JSON schema string** — a file path makes the CLI exit 0 with empty output. The action then finds no `structured_output` and hits its fail-loud branch, so a consumer who follows the documented recipe gets a step that fails closed on every run, with no useful diagnostic. ## Fix This implements the issue's **proposed solution #2 (auto-inline the file)**, plus the doc clarification from #1. I chose #2 because it dominates the alternatives: - **Doc-only (#1):** would push everyone to the inline single-quoted form, reintroducing the exact shell `$`-expansion hazard the file-path form exists to avoid for schemas with `$ref`/`$defs`. - **Bump the default CLI (#3):** unverified, and changes the default for every consumer. `prepareRunConfig` now resolves each `--json-schema` value (both `--json-schema <v>` and `--json-schema=<v>`) before spawning the CLI: - A value that's **already inline JSON** (trimmed, starts with `{`/`[`) is left exactly as supplied. - A **file path** is read in-process, validated, and passed **inline** as a compact JSON string. Because the value reaches the CLI as an argv element (never through a shell), a schema containing `$ref`/`$defs` round-trips intact — and it works on any CLI, including the default 2.1.160. - An **unreadable file or invalid JSON** now fails with a clear, specific error instead of the CLI's silent empty output. Docs (README structured-output notes, the example workflow, and the `action.yml` `claude_args` description) are corrected to explain the file-path form is auto-inlined and works on the default CLI. The narrowed CLI-version caveat is kept (a pinned/pre-baked CLI must still support the `--json-schema` flag itself). ## Tests Added an `inlineJsonSchemaArgs` suite (real temp files): inline passthrough, file-path inlining for the space and equals forms, the **`$ref`/`$defs` from file round-trips intact** case, missing-file and invalid-JSON error paths, plus an end-to-end `prepareRunConfig` test. `bun test` on `run-claude.test.ts`: 45 pass / 0 fail. `tsc --noEmit` clean (base-action + root). If you'd actually prefer solution #1 or #3 instead, happy to redirect.
ryan added 1 commit 2026-06-03 02:46:20 +02:00
The docs recommend the file-path form (`--json-schema /path/to/schema.json`)
as primary, but the default-pinned Claude CLI 2.1.160 only accepts an inline
JSON string. Passing a path makes the CLI exit 0 with empty output, so the
action's fail-loud branch trips on every run.

Resolve the value before spawning the CLI: a file-path schema is read
in-process and passed inline (validated + compacted), while an already-inline
schema is left untouched. Because the value is handed to spawn() as an argv
element (never through a shell), a schema with `$ref`/`$defs` round-trips
intact. Unreadable files and invalid JSON now fail with a clear error instead
of the CLI's silent empty output.

Docs (README, example workflow, action.yml) are corrected to explain that the
file-path form is auto-inlined and works on the default CLI.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ryan merged commit 274d7cb29b into gitea 2026-06-03 02:52:33 +02:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: ryan/claude-code-gitea-action#4