Use tinycode run directly in GitHub Actions for CI-driven agent loops. There is no official GitHub Action package — install the binary and invoke tinycode run in workflow YAML. That keeps CI local-first: your workflow owns the model, secrets, and permissions.
For full run-mode flags and NDJSON event types, see Run Mode in the user guide.
- A model tinycode can reach from the runner (cloud API or a reachable OpenAI-compatible endpoint)
- Repository secrets for API keys and the model id (see Secrets)
- A Linux or macOS runner (
ubuntu-latestis typical)
Install the binary once per job (same installer as local use):
- name: Install tinycode
run: |
curl -fsSL https://raw.githubusercontent.com/bobbyjohnstx/tinycode/main/install.sh | sh
echo "$HOME/.local/bin" >> "$GITHUB_PATH"Review a pull request when it opens or updates. Redirect stdin from /dev/null so the job does not hang waiting for input.
name: tinycode PR review
on:
pull_request:
types: [opened, synchronize, reopened]
permissions:
contents: read
pull-requests: write
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install tinycode
run: |
curl -fsSL https://raw.githubusercontent.com/bobbyjohnstx/tinycode/main/install.sh | sh
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Run tinycode
env:
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
run: |
# Default build agent already allows ordinary shell/edit/read.
# Add --dangerously-skip-permissions only if the job must auto-approve
# residual Ask classes (webfetch, .env*, destructive/secret-shell, external_directory).
tinycode run --format json \
-m "${{ secrets.MODEL }}" \
"Review this PR for issues. Summarize findings clearly." \
< /dev/null \
| tee tinycode-out.ndjsonOrdinary shell / edit / read work under the default build agent without --dangerously-skip-permissions. Headless auto-reject applies only to residual Ask classes: webfetch, read .env*, destructive-shell, secret-shell, and external_directory.
The question tool is fail-closed in tinycode run: asks are rejected immediately as unsupported in non-interactive mode (no stdin wait).
Respond when someone posts a PR comment that starts with /tinycode:
name: tinycode comment
on:
issue_comment:
types: [created]
permissions:
contents: read
pull-requests: write
issues: write
jobs:
run:
if: >
github.event.issue.pull_request &&
startsWith(github.event.comment.body, '/tinycode')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: refs/pull/${{ github.event.issue.number }}/head
- name: Install tinycode
run: |
curl -fsSL https://raw.githubusercontent.com/bobbyjohnstx/tinycode/main/install.sh | sh
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Run tinycode from comment
env:
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
PROMPT: ${{ github.event.comment.body }}
run: |
# Strip the leading /tinycode token; rest is the prompt
PROMPT_TEXT="${PROMPT#/tinycode}"
PROMPT_TEXT="${PROMPT_TEXT# }"
if [ -z "$PROMPT_TEXT" ]; then
PROMPT_TEXT="Review this pull request and summarize findings."
fi
tinycode run --format json \
-m "${{ secrets.MODEL }}" \
"$PROMPT_TEXT" \
< /dev/null \
| tee tinycode-out.ndjson
- name: Post reply from JSON output
env:
GH_TOKEN: ${{ github.token }}
run: |
# Concatenate model text deltas into one comment body
REPLY=$(jq -r 'select(.type == "text") | .text // empty' tinycode-out.ndjson | tr -d '\n')
if [ -z "$REPLY" ]; then
REPLY=$(jq -r 'select(.type == "error") | .message // "tinycode produced no text output"' tinycode-out.ndjson | tail -n1)
fi
gh pr comment "${{ github.event.issue.number }}" --body "$REPLY"The workflow calls tinycode run only — no custom Action marketplace package.
Store credentials and the model id in GitHub Actions secrets (Settings → Secrets and variables → Actions). Do not hardcode keys in YAML.
| Secret | Purpose |
|---|---|
OPENROUTER_API_KEY |
Enables OpenRouter discovery/auth (common cloud path) |
MODEL |
Model id passed to -m (for example openrouter/anthropic/claude-sonnet-4) |
Other providers:
| Env / secret | Notes |
|---|---|
TINYCODE_OLLAMA_HOST / OLLAMA_HOST |
Reachable Ollama base URL (self-hosted runner or network path) |
TINYCODE_VLLM_HOST |
vLLM OpenAI-compatible endpoint |
TINYCODE_LMSTUDIO_HOST |
LM Studio endpoint |
TINYCODE_MAAS_API_KEY |
MaaS auth when using that provider |
Wire secrets into the step env: block so tinycode can discover the provider at startup:
env:
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}Model selection:
tinycode run -m "${{ secrets.MODEL }}" "…"Or pin a literal model in the workflow if it is not sensitive (prefer a secret when the id encodes a paid route).
The default build agent already allows ordinary shell, edit, and read without this flag. Use --dangerously-skip-permissions when you intentionally want unattended auto-approval of residual Ask-class permissions (and anything else that would prompt): webfetch, .env* reads, destructive/secret shell, and paths outside the project.
Risks with the flag:
- Ask-class tools (network fetch, secret-file shell, destructive shell, external dirs) run without a human gate
- Compromised prompts (for example from untrusted PR content or comments) can drive those higher-risk tools
- Secrets available to the job process may be readable by tools the agent invokes
Mitigations:
- Omit the flag for ordinary edit/shell CI work; add it only when Ask-class tools must succeed unattended
- Scope
permissions:tightly (contents: readunless the job must push) - Restrict which events and actors can trigger agent jobs (for example require a collaborator comment)
- Prefer
--safe-modewhen plugins/MCP/user agents are not needed - Cap cost with
--max-tokensand--max-iterations - Run on
pull_requestfrom forks only with secrets policies you understand (fork PRs often cannot access repository secrets)
Default headless behavior without the flag: build allows ordinary shell/edit/read; residual Ask requests are auto-rejected. Interactive (-i) and JSON permission protocols are a poor fit for most Actions jobs.
--format json emits NDJSON (one JSON object per line). Useful event types for CI:
type |
Use in CI |
|---|---|
session |
Capture sessionID for logs or follow-up runs |
text |
Append text deltas to build the assistant reply |
tool_begin / tool_end |
Optional progress / failure diagnostics |
done |
Success when ok is true; ok: false means a tool returned isError (exit 1). Use --no-fail-on-tool-error to keep exit 0 / ok: true |
error |
Failure message (processor/turn failure; cancel is exit 130) |
Example: collect the reply and fail the job if the run did not finish cleanly:
tinycode run --format json \
-m "${{ secrets.MODEL }}" \
"Summarize the diff risk in this checkout." \
< /dev/null \
| tee tinycode-out.ndjson
REPLY=$(jq -r 'select(.type == "text") | .text // empty' tinycode-out.ndjson | tr -d '\n')
OK=$(jq -r 'select(.type == "done") | .ok' tinycode-out.ndjson | tail -n1)
echo "$REPLY"
if [ "$OK" != "true" ]; then
jq -r 'select(.type == "error") | .message // empty' tinycode-out.ndjson >&2
exit 1
fiPost that reply with gh pr comment (as in the comment-triggered example) or gh api if you prefer raw REST.
Text format (--format default, the default) is fine for logs; prefer --format json whenever another step must parse the answer.
- Run Mode — flags, permissions, multi-turn NDJSON protocol
- Install — installer env vars and platforms
- Cheatsheet — short
tinycode runexamples