Extend LoopCode
Headless mode and CI
Run LoopCode without a screen from scripts, pipelines and CI — prompts in, answers out, JSON output, exit codes, permissions and access tokens.
Run a prompt
loopcode -p "Explain what src/billing/invoice.ts exports"
With -p, LoopCode runs one prompt without a screen. The answer goes to stdout, tool activity to stderr, and the exit code says how the run ended — so it composes with pipes and scripts:
git diff | loopcode -p "Write a commit message for this diff" > message.txt
echo "list the TODOs in src/" | loopcode -p
A prompt can come from standard input when -p is given without one.
Options
| Flag | Meaning |
|---|---|
-p, --prompt <text> | The prompt |
--output-format text | The answer as text (the default) |
--output-format json | One result object at the end |
--output-format stream-json, --json | Every run event as a JSON line, as it happens |
-q, --quiet | No tool activity on stderr |
--max-turns <n> | Stop after this many model calls |
--append-system-prompt <text> | Extra instructions for this run |
-c, --continue | Continue the most recent conversation in this folder |
-r, --resume <id> | Continue a particular conversation (loopcode sessions lists them) |
-m, --model <id> | The model |
--mode <mode> | The approval mode |
--effort <level> | Reasoning effort |
-C <dir> | Run in another folder |
-w [name] | Run in a new git worktree (kept if it has changes) |
-y, --yes | Approve every permission the run asks for |
JSON output
loopcode -p "How many API routes are there?" --output-format json
{
"type": "result",
"subtype": "success",
"is_error": false,
"result": "There are 37 routes …",
"session_id": "3f2a9c1e-…",
"num_turns": 4,
"duration_ms": 18240,
"usage": { "total_tokens": 48211, "cached_tokens": 31024 }
}subtype is success, or error_<status> when the run did not finish. session_id resumes the conversation with -r.
With stream-json, every event the run produces is printed as one JSON line — the same records the desktop app shows.
Exit codes
| Code | Meaning |
|---|---|
0 | The run finished successfully |
1 | The run ended without success (stopped, failed, or out of turns) |
2 | It could not start: a bad option, no prompt, or not signed in |
130 | Interrupted with ctrl c |
Permissions
Nobody is there to answer in a headless run, so a permission the mode and rules do not cover is denied, and the agent finds another way or reports that it could not. Give a pipeline exactly what it needs with permission rules in the repository's settings:
{
"permissions": {
"allow": ["Bash(npm ci)", "Bash(npm test:*)", "Bash(npm run lint)", "Edit(src/**)", "Edit(test/**)"],
"deny": ["Bash(git push:*)", "Read(./.env)"]
}
}-y approves everything the run asks for, and --dangerously-skip-permissions stops asking altogether. Use either only in a disposable container with no production credentials.
Access tokens
CI has no browser to sign in with. Create an access token, and set it as LOOPCODE_TOKEN:
loopcode token create --label "CI"
The token is shown once. It draws on your plan, like the app and the CLI.
loopcode token list # your tokens, with when each was last used
loopcode token revoke <id> # switch one off; anything using it stops at its next request
A CI job
name: Explain test failures
on: [workflow_dispatch]
jobs:
explain:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install LoopCode
run: |
curl -fsSL https://loopcode.heksync.in/cli.sh | LOOPCODE_NO_LOGIN=1 LOOPCODE_NO_MODIFY_PATH=1 sh
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Run
env:
LOOPCODE_TOKEN: ${{ secrets.LOOPCODE_TOKEN }}
run: loopcode -p "Run the tests. For each failure, explain the cause in two sentences." --max-turns 30 --output-format json > result.json
For pull request reviews and @loopcode mentions, use the ready-made integration: GitHub reviews and fixes.
Before you rely on it
Test the path where things go wrong as well as the happy one: a permission that is denied, a model or network error, a run that hits --max-turns. Check the exit code and subtype, not just whether text came out.