feat: add claude_args passthrough + structured_output output (#1) #2

Merged
ryan merged 1 commits from feat/claude-args-structured-output into gitea 2026-06-02 14:33:57 +02:00
Owner

Closes #1.

Brings the upstream v1 claude_args passthrough input and structured_output output into this Gitea fork, so downstream workflows can run the agent with arbitrary Claude CLI flags — notably --json-schema — and read the schema-validated verdict back as an action output instead of an agent-written file, while keeping the Gitea adaptations this fork exists for.

Approach — issue Option B, ported (not bumped)

The production Run Claude Code step previously delegated to the external anthropics/claude-code-base-action@v0.0.63, which has neither input. Upstream base-action main has them, but has since migrated to the Claude Agent SDK — a wholesale architectural divergence that would change the v0.0.63 prompt-file/mcp-config/stream-json behavior every downstream step (review/docs/conflict/triage/@claude) relies on. So instead of bumping the pin to an untagged main SHA, this:

  1. Invokes the vendored base-action/ directly via bun run ${{ github.action_path }}/base-action/src/index.ts — the same github.action_path pattern prepare.ts/update-comment-link.ts already use in production (reliable on Gitea; a relative uses: ./base-action is not, as Gitea resolves it against $GITHUB_WORKSPACE). This also removes the old double-CLI-install ambiguity.
  2. Ports only the two features onto the existing v0.0.63 process-spawn engine, so everything else is byte-identical.

Changes

  • base-action/src/run-claude.ts — tokenize claude_args with shell-quote (full-comment-line stripping; quoted / file-path schemas survive intact) and append the tokens after the unchanged BASE_ARGS, so empty claude_args yields an identical arg list. Extract structured_output from the stream-json result event when --json-schema is present; fail loudly (non-zero exit, conclusion=failure) if the schema was requested but none came back.
  • base-action/src/index.ts — forward INPUT_CLAUDE_ARGS.
  • base-action/action.yml — add claude_args input + structured_output output.
  • action.yml — add claude_args input + structured_output output; replace the external base-action delegation with the direct bun run (full INPUT_*/provider env contract derived from the vendored validate-env/index); bump the default Claude CLI install 1.0.117 → 2.1.160 (--json-schema needs a newer CLI).
  • depsshell-quote added to both package.json files. The root bun.lock is reconciled as a side effect: it was already stale vs package.json (locked @octokit/rest@21 while ^22 is declared), so the action's non-frozen bun install already resolved the newer tree at runtime — this just makes the committed lockfile honest. shell-quote must live in the root because production resolves it from the root node_modules via walk-up.
  • testsparseClaudeArgs (incl. a $ref schema round-trip), hasJsonSchema detection, extractStructuredOutput, and a byte-identical-when-empty guard. Full base-action suite green; root suite green (the 3 updateCommentBody failures are pre-existing order-dependent flakes, present on gitea too).
  • docs — README inputs/outputs tables, a Structured output section, a Version pins table, and examples/gitea-structured-output.yml.

Verification

Beyond unit tests, the rewired entrypoint was driven end-to-end with base-action/node_modules removed (production layout) and a stubbed claude:

  • no claude_argsexecution_file + conclusion=success, no structured_output (behave-unchanged);
  • --json-schemastructured_output populated, conclusion=success;
  • --json-schema with no structured result → conclusion=failure, exit 1.

This confirms shell-quote/@actions/core resolve from the root node_modules, the named-pipe orchestration runs, and the outputs wire through.

Follow-up (cannot be done from this repo — acceptance criterion #4)

Consumers using a pre-baked runner image that sets path_to_claude_code_executable skip the action's install step, so the baked CLI must be bumped independently: rebuild that image with a --json-schema-capable CLAUDE_CLI_VERSION and restart the runner. Documented under README → Version pins.

Accuracy notes

  • On a pinned-CLI runner this does not change which CLI version runs in a way I can claim is identical — the old flow installed two CLIs (base-action's @1.0.61 plus the baked one) and PATH order decided the winner; the new flow deterministically uses the runner's pinned path_to_claude_code_executable.
  • The structured_output field name is taken from the SDK's result message and is expected to match CLI 2.1.160's stream-json result event; a real --json-schema run on the rebuilt runner should confirm it (the fail-loud guard degrades safely if it differs).

🤖 Generated with Claude Code

Closes #1. Brings the upstream v1 `claude_args` passthrough **input** and `structured_output` **output** into this Gitea fork, so downstream workflows can run the agent with arbitrary Claude CLI flags — notably `--json-schema` — and read the schema-validated verdict back as an action output instead of an agent-written file, while keeping the Gitea adaptations this fork exists for. ## Approach — issue Option B, *ported* (not bumped) The production **Run Claude Code** step previously delegated to the external `anthropics/claude-code-base-action@v0.0.63`, which has neither input. Upstream base-action `main` has them, but has since migrated to the **Claude Agent SDK** — a wholesale architectural divergence that would change the v0.0.63 prompt-file/mcp-config/stream-json behavior **every** downstream step (review/docs/conflict/triage/@claude) relies on. So instead of bumping the pin to an untagged `main` SHA, this: 1. **Invokes the vendored `base-action/` directly** via `bun run ${{ github.action_path }}/base-action/src/index.ts` — the same `github.action_path` pattern `prepare.ts`/`update-comment-link.ts` already use in production (reliable on Gitea; a relative `uses: ./base-action` is **not**, as Gitea resolves it against `$GITHUB_WORKSPACE`). This also removes the old double-CLI-install ambiguity. 2. **Ports only the two features** onto the existing v0.0.63 process-spawn engine, so everything else is byte-identical. ## Changes - **`base-action/src/run-claude.ts`** — tokenize `claude_args` with `shell-quote` (full-comment-line stripping; quoted / file-path schemas survive intact) and append the tokens **after** the unchanged `BASE_ARGS`, so empty `claude_args` yields an identical arg list. Extract `structured_output` from the stream-json `result` event when `--json-schema` is present; **fail loudly** (non-zero exit, `conclusion=failure`) if the schema was requested but none came back. - **`base-action/src/index.ts`** — forward `INPUT_CLAUDE_ARGS`. - **`base-action/action.yml`** — add `claude_args` input + `structured_output` output. - **`action.yml`** — add `claude_args` input + `structured_output` output; replace the external base-action delegation with the direct `bun run` (full `INPUT_*`/provider env contract derived from the **vendored** `validate-env`/`index`); bump the default Claude CLI install `1.0.117 → 2.1.160` (`--json-schema` needs a newer CLI). - **deps** — `shell-quote` added to both `package.json` files. The root `bun.lock` is reconciled as a side effect: it was already **stale** vs `package.json` (locked `@octokit/rest@21` while `^22` is declared), so the action's non-frozen `bun install` already resolved the newer tree at runtime — this just makes the committed lockfile honest. `shell-quote` must live in the **root** because production resolves it from the root `node_modules` via walk-up. - **tests** — `parseClaudeArgs` (incl. a `$ref` schema round-trip), `hasJsonSchema` detection, `extractStructuredOutput`, and a byte-identical-when-empty guard. Full base-action suite green; root suite green (the 3 `updateCommentBody` failures are pre-existing order-dependent flakes, present on `gitea` too). - **docs** — README inputs/outputs tables, a **Structured output** section, a **Version pins** table, and `examples/gitea-structured-output.yml`. ## Verification Beyond unit tests, the rewired entrypoint was driven end-to-end with `base-action/node_modules` **removed** (production layout) and a stubbed `claude`: - no `claude_args` → `execution_file` + `conclusion=success`, **no** `structured_output` (behave-unchanged); - `--json-schema` → `structured_output` populated, `conclusion=success`; - `--json-schema` with no structured result → `conclusion=failure`, exit 1. This confirms `shell-quote`/`@actions/core` resolve from the root `node_modules`, the named-pipe orchestration runs, and the outputs wire through. ## Follow-up (cannot be done from this repo — acceptance criterion #4) Consumers using a **pre-baked runner image** that sets `path_to_claude_code_executable` skip the action's install step, so the baked CLI must be bumped independently: rebuild that image with a `--json-schema`-capable `CLAUDE_CLI_VERSION` and restart the runner. Documented under README → *Version pins*. ## Accuracy notes - On a pinned-CLI runner this does **not** change which CLI version runs in a way I can claim is identical — the old flow installed *two* CLIs (base-action's `@1.0.61` plus the baked one) and PATH order decided the winner; the new flow **deterministically uses the runner's pinned `path_to_claude_code_executable`**. - The `structured_output` field name is taken from the SDK's result message and is **expected to** match CLI `2.1.160`'s stream-json `result` event; a real `--json-schema` run on the rebuilt runner should confirm it (the fail-loud guard degrades safely if it differs). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
ryan added 1 commit 2026-06-02 14:27:57 +02:00
Bring the upstream v1 `claude_args` input and `structured_output` output
into this Gitea fork so downstream workflows can run the agent with
arbitrary Claude CLI flags (notably `--json-schema`) and read the
schema-validated verdict back as an action output, without an
agent-written file.

Approach (issue Option B, ported rather than bumped): the production
"Run Claude Code" step previously delegated to the external
anthropics/claude-code-base-action@v0.0.63, which has neither input.
Upstream base-action main has them but has migrated to the Claude Agent
SDK — a wholesale divergence that would change v0.0.63 behavior every
downstream step relies on. Instead this invokes the vendored base-action
directly via `bun run ${github.action_path}/base-action/src/index.ts`
(the same github.action_path pattern prepare.ts already uses, reliable
on Gitea), and ports only the two features onto the v0.0.63 process-spawn
engine.

- base-action/src/run-claude.ts: tokenize claude_args with shell-quote
  (comment-line stripping; quoted/file-path schemas survive intact) and
  append after the byte-identical BASE_ARGS, so empty claude_args yields
  an unchanged arg list; extract structured_output from the stream-json
  result event when --json-schema is present, failing loudly if absent.
- base-action/src/index.ts: forward INPUT_CLAUDE_ARGS.
- base-action/action.yml: add claude_args input + structured_output output.
- action.yml: add claude_args input + structured_output output; replace
  the external base-action delegation with a direct bun-run of the
  vendored copy (full INPUT_*/provider env contract derived from the
  vendored validate-env/index); bump the default Claude CLI install to
  2.1.160 (--json-schema requires a newer CLI than 1.0.117).
- shell-quote added to both package.json files; root bun.lock reconciled
  (it was stale vs package.json — non-frozen installs already resolved
  the newer tree).
- Tests for tokenizer, hasJsonSchema, and structured-output extraction.
- README: inputs/outputs tables, "Structured output" + "Version pins".
- examples/gitea-structured-output.yml.

Follow-up (cannot be done from this repo): consumers using a pre-baked
runner image (path_to_claude_code_executable) must rebuild that image
with a --json-schema-capable Claude CLI and restart the runner.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ryan merged commit 13cde95d51 into gitea 2026-06-02 14:33:57 +02:00
ryan deleted branch feat/claude-args-structured-output 2026-06-02 14:33:57 +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#2