Files
claude-code-gitea-action/action.yml
T
reza 52882e1d74 feat: add claude_args passthrough and structured_output (closes #1)
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>
2026-06-02 22:22:51 +10:00

295 lines
13 KiB
YAML

name: "Claude Code Action Official"
description: "General-purpose Claude agent for GitHub PRs and issues. Can answer questions and implement code changes."
branding:
icon: "at-sign"
color: "orange"
inputs:
trigger_phrase:
description: "The trigger phrase to look for in comments or issue body"
required: false
default: "@claude"
assignee_trigger:
description: "The assignee username that triggers the action (e.g. @claude)"
required: false
label_trigger:
description: "The label that triggers the action (e.g. claude)"
required: false
default: "claude"
branch_prefix:
description: "The prefix to use for Claude branches (defaults to 'claude/', use 'claude-' for dash format)"
required: false
default: "claude/"
mode:
description: "Execution mode for the action. Valid modes: 'tag' (default) or 'agent'"
required: false
default: "tag"
base_branch:
description: "The branch to use as the base/source when creating new branches (defaults to repository default branch)"
required: false
# Claude Code configuration
model:
description: "Model to use (provider-specific format required for Bedrock/Vertex)"
required: false
anthropic_model:
description: "DEPRECATED: Use 'model' instead. Model to use (provider-specific format required for Bedrock/Vertex)"
required: false
allowed_tools:
description: "Additional tools for Claude to use (the base GitHub tools will always be included)"
required: false
default: ""
disallowed_tools:
description: "Tools that Claude should never use"
required: false
default: ""
custom_instructions:
description: "Additional custom instructions to include in the prompt for Claude"
required: false
default: ""
direct_prompt:
description: "Direct instruction for Claude (bypasses normal trigger detection)"
required: false
default: ""
override_prompt:
description: "Complete replacement of Claude's prompt with custom template (supports variable substitution)"
required: false
default: ""
# New Claude Code settings
settings:
description: "Path to Claude Code settings JSON file, or settings JSON string"
required: false
default: ""
system_prompt:
description: "Override system prompt"
required: false
default: ""
append_system_prompt:
description: "Append to system prompt"
required: false
default: ""
claude_env:
description: "Custom environment variables to pass to Claude Code execution (YAML multiline format)"
required: false
default: ""
additional_permissions:
description: "Additional permissions to enable. Currently supports 'actions: read' for viewing workflow results"
required: false
default: ""
fallback_model:
description: "Enable automatic fallback to specified model when default model is overloaded"
required: false
default: ""
claude_args:
description: "Additional arguments to pass directly to the Claude CLI, forwarded verbatim (e.g. '--json-schema /path/to/schema.json' to get schema-validated output via the structured_output output). Requires a Claude CLI new enough to support the flags used (see README)."
required: false
default: ""
# Auth configuration
anthropic_api_key:
description: "Anthropic API key (required for direct API, not needed for Bedrock/Vertex)"
required: false
claude_code_oauth_token:
description: "Claude Code OAuth token (alternative to anthropic_api_key)"
required: false
default: ""
gitea_token:
description: "Gitea token with repo and pull request permissions (defaults to GITHUB_TOKEN)"
required: false
path_to_claude_code_executable:
description: "Path to a custom Claude Code executable to use instead of installing"
required: false
default: ""
use_bedrock:
description: "Use Amazon Bedrock with OIDC authentication instead of direct Anthropic API"
required: false
default: "false"
use_vertex:
description: "Use Google Vertex AI with OIDC authentication instead of direct Anthropic API"
required: false
default: "false"
use_node_cache:
description: "Whether to use Node.js dependency caching (set to true only for Node.js projects with lock files)"
required: false
default: "false"
max_turns:
description: "Maximum number of conversation turns"
required: false
default: ""
timeout_minutes:
description: "Timeout in minutes for execution"
required: false
default: "30"
claude_git_name:
description: "Git user.name for commits made by Claude"
required: false
default: "Claude"
claude_git_email:
description: "Git user.email for commits made by Claude"
required: false
default: "claude@anthropic.com"
outputs:
execution_file:
description: "Path to the Claude Code execution output file"
value: ${{ steps.claude-code.outputs.execution_file }}
structured_output:
description: "JSON string of the schema-validated result when --json-schema is provided in claude_args (parse with fromJSON() or jq)"
value: ${{ steps.claude-code.outputs.structured_output }}
branch_name:
description: "The branch created by Claude Code for this execution"
value: ${{ steps.prepare.outputs.CLAUDE_BRANCH }}
runs:
using: "composite"
steps:
- name: Install Bun
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # https://github.com/oven-sh/setup-bun/releases/tag/v2.0.2
with:
bun-version: 1.2.11
- name: Install Dependencies
shell: bash
run: |
cd ${{ github.action_path }}
bun install
- name: Prepare action
id: prepare
shell: bash
run: |
bun run ${{ github.action_path }}/src/entrypoints/prepare.ts
env:
MODE: ${{ inputs.mode }}
TRIGGER_PHRASE: ${{ inputs.trigger_phrase }}
ASSIGNEE_TRIGGER: ${{ inputs.assignee_trigger }}
LABEL_TRIGGER: ${{ inputs.label_trigger }}
BASE_BRANCH: ${{ inputs.base_branch }}
BRANCH_PREFIX: ${{ inputs.branch_prefix }}
ALLOWED_TOOLS: ${{ inputs.allowed_tools }}
DISALLOWED_TOOLS: ${{ inputs.disallowed_tools }}
CUSTOM_INSTRUCTIONS: ${{ inputs.custom_instructions }}
DIRECT_PROMPT: ${{ inputs.direct_prompt }}
OVERRIDE_PROMPT: ${{ inputs.override_prompt }}
ADDITIONAL_PERMISSIONS: ${{ inputs.additional_permissions }}
OVERRIDE_GITHUB_TOKEN: ${{ inputs.gitea_token }}
GITHUB_TOKEN: ${{ github.token }}
GITHUB_RUN_ID: ${{ github.run_id }}
GITEA_API_URL: ${{ env.GITHUB_SERVER_URL }}
ANTHROPIC_API_KEY: ${{ inputs.anthropic_api_key }}
- name: Install Claude
if: steps.prepare.outputs.contains_trigger == 'true'
shell: bash
run: |
# Install Claude Code if no custom executable is provided.
# Version must be new enough to support any flags passed via claude_args
# (e.g. --json-schema). Runner images that bake their own CLI via
# path_to_claude_code_executable must bump their pin independently.
if [ -z "${{ inputs.path_to_claude_code_executable }}" ]; then
echo "Installing Claude Code..."
curl -fsSL https://claude.ai/install.sh | bash -s 2.1.160
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
else
echo "Using custom Claude Code executable: ${{ inputs.path_to_claude_code_executable }}"
# Add the directory containing the custom executable to PATH
CLAUDE_DIR=$(dirname "${{ inputs.path_to_claude_code_executable }}")
echo "$CLAUDE_DIR" >> "$GITHUB_PATH"
fi
# TODO pass claude_code_executable as input and use it here
- name: Run Claude Code
id: claude-code
if: steps.prepare.outputs.contains_trigger == 'true'
shell: bash
run: |
bun run ${{ github.action_path }}/base-action/src/index.ts
env:
# Core configuration. The vendored base-action reads these INPUT_* env
# vars directly (see base-action/src/index.ts), so we map them here
# instead of delegating to the external claude-code-base-action. This
# lets us forward claude_args and surface structured_output while
# keeping the Gitea adaptations this fork relies on.
CLAUDE_CODE_ACTION: "1"
INPUT_PROMPT_FILE: /tmp/claude-prompts/claude-prompt.txt
INPUT_ALLOWED_TOOLS: ${{ env.ALLOWED_TOOLS }}
INPUT_DISALLOWED_TOOLS: ${{ env.DISALLOWED_TOOLS }}
INPUT_MAX_TURNS: ${{ inputs.max_turns }}
INPUT_TIMEOUT_MINUTES: ${{ inputs.timeout_minutes }}
INPUT_MCP_CONFIG: ${{ steps.prepare.outputs.mcp_config }}
INPUT_SETTINGS: ${{ inputs.settings }}
INPUT_SYSTEM_PROMPT: ${{ inputs.system_prompt }}
INPUT_APPEND_SYSTEM_PROMPT: ${{ inputs.append_system_prompt }}
INPUT_CLAUDE_ENV: ${{ inputs.claude_env }}
INPUT_FALLBACK_MODEL: ${{ inputs.fallback_model }}
INPUT_CLAUDE_ARGS: ${{ inputs.claude_args }}
ANTHROPIC_MODEL: ${{ inputs.model || inputs.anthropic_model }}
# Provider selection (base-action validate-env reads the '1'/'' form).
CLAUDE_CODE_USE_BEDROCK: ${{ inputs.use_bedrock == 'true' && '1' || '' }}
CLAUDE_CODE_USE_VERTEX: ${{ inputs.use_vertex == 'true' && '1' || '' }}
# Auth configuration
ANTHROPIC_API_KEY: ${{ inputs.anthropic_api_key }}
CLAUDE_CODE_OAUTH_TOKEN: ${{ inputs.claude_code_oauth_token }}
# GitHub token for repository access
GITHUB_TOKEN: ${{ steps.prepare.outputs.GITHUB_TOKEN }}
GITEA_API_URL: ${{ env.GITHUB_SERVER_URL }}
# Git configuration
CLAUDE_GIT_NAME: ${{ inputs.claude_git_name }}
CLAUDE_GIT_EMAIL: ${{ inputs.claude_git_email }}
# Provider configuration (for cloud provider support)
ANTHROPIC_BASE_URL: ${{ env.ANTHROPIC_BASE_URL }}
AWS_REGION: ${{ env.AWS_REGION }}
AWS_ACCESS_KEY_ID: ${{ env.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ env.AWS_SECRET_ACCESS_KEY }}
AWS_SESSION_TOKEN: ${{ env.AWS_SESSION_TOKEN }}
ANTHROPIC_BEDROCK_BASE_URL: ${{ env.ANTHROPIC_BEDROCK_BASE_URL }}
ANTHROPIC_VERTEX_PROJECT_ID: ${{ env.ANTHROPIC_VERTEX_PROJECT_ID }}
CLOUD_ML_REGION: ${{ env.CLOUD_ML_REGION }}
GOOGLE_APPLICATION_CREDENTIALS: ${{ env.GOOGLE_APPLICATION_CREDENTIALS }}
ANTHROPIC_VERTEX_BASE_URL: ${{ env.ANTHROPIC_VERTEX_BASE_URL }}
VERTEX_REGION_CLAUDE_3_5_HAIKU: ${{ env.VERTEX_REGION_CLAUDE_3_5_HAIKU }}
VERTEX_REGION_CLAUDE_3_5_SONNET: ${{ env.VERTEX_REGION_CLAUDE_3_5_SONNET }}
VERTEX_REGION_CLAUDE_3_7_SONNET: ${{ env.VERTEX_REGION_CLAUDE_3_7_SONNET }}
- name: Update comment with job link
if: steps.prepare.outputs.contains_trigger == 'true' && steps.prepare.outputs.claude_comment_id && always()
shell: bash
run: |
bun run ${{ github.action_path }}/src/entrypoints/update-comment-link.ts
env:
REPOSITORY: ${{ github.repository }}
PR_NUMBER: ${{ github.event.issue.number || github.event.pull_request.number }}
CLAUDE_COMMENT_ID: ${{ steps.prepare.outputs.claude_comment_id }}
GITHUB_RUN_ID: ${{ github.run_id }}
GITHUB_TOKEN: ${{ steps.prepare.outputs.GITHUB_TOKEN }}
GITHUB_EVENT_NAME: ${{ github.event_name }}
TRIGGER_COMMENT_ID: ${{ github.event.comment.id }}
CLAUDE_BRANCH: ${{ steps.prepare.outputs.CLAUDE_BRANCH }}
IS_PR: ${{ github.event.issue.pull_request != null || github.event_name == 'pull_request_review_comment' }}
BASE_BRANCH: ${{ steps.prepare.outputs.BASE_BRANCH }}
CLAUDE_SUCCESS: ${{ steps.claude-code.outputs.conclusion == 'success' }}
OUTPUT_FILE: ${{ steps.claude-code.outputs.execution_file || '' }}
TRIGGER_USERNAME: ${{ github.event.comment.user.login || github.event.issue.user.login || github.event.pull_request.user.login || github.event.sender.login || github.triggering_actor || github.actor || '' }}
PREPARE_SUCCESS: ${{ steps.prepare.outcome == 'success' }}
PREPARE_ERROR: ${{ steps.prepare.outputs.prepare_error || '' }}
GITEA_API_URL: ${{ env.GITHUB_SERVER_URL }}
- name: Display Claude Code Report
if: steps.prepare.outputs.contains_trigger == 'true' && steps.claude-code.outputs.execution_file != ''
shell: bash
run: |
if [ -f "${{ steps.claude-code.outputs.execution_file }}" ]; then
echo "## Claude Code Report" >> $GITHUB_STEP_SUMMARY
echo '```json' >> $GITHUB_STEP_SUMMARY
cat "${{ steps.claude-code.outputs.execution_file }}" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Claude Code execution completed but no report file was generated" >> $GITHUB_STEP_SUMMARY
fi