Runners
june routes one Linear issue to one runner. The runner is a CLI on the host that june spawns as a child process, feeds the prompt over `argv`, and reads the reply back from stdout. The default is Claude Code; per-issue L
june routes one Linear issue to one runner. The runner is a CLI on the host that june spawns as a child process, feeds the prompt over argv, and reads the reply back from stdout. The default is Claude Code; per-issue Linear labels switch to alternatives.
The runner table
| Label | Runner CLI | Stateful? | Good for |
|---|---|---|---|
| (none) | Claude Code (claude) | Yes | The default. Strong long-horizon coding agent with Read/Edit/Bash. Stream-JSON output, resumable sessions. |
codex | Codex CLI (codex exec) | Yes | Heavy reasoning. june forces --effort xhigh for this label. Persists its own thread/session id. |
opencode | OpenCode (opencode run) | Yes | Open-source agent. Sandbox-friendly: ships with a default OPENCODE_PERMISSION that denies external-directory access and arbitrary bash. |
gemini | Antigravity CLI (agy) | No (per-run) | Google's successor to the deprecated gemini CLI. Model is bound to the desktop session, not env-configurable. |
hermes | Hermes Agent (hermes -z) | No (per-run) | Lightweight, provider-agnostic. Each Linear comment spawns a fresh session. Toolset is configurable. |
"Stateful" means the runner exposes a resumable session id that june stores and passes back on the next comment. Stateless runners get a fresh process per comment — they can still steer mid-run, they just don't remember previous turns.
No labels = Claude Code with effort max (hardcoded — Claude doesn't persist /effort between runs, so the orchestrator passes --effort max per spawn).
How label routing works
- june reads the issue's labels (case-insensitive).
- The first matching runner label wins:
codex>opencode>gemini>hermes. - If the corresponding runner CLI was missing at boot, june falls back to Claude and posts a one-line comment on the issue explaining what happened.
- Switching the label between runs starts a fresh session in the new runner's column. Claude / Codex / OpenCode / Hermes session ids are stored separately, so flipping back picks up the old session.
The label name is configurable: JUNE_CODEX_LABEL, JUNE_OPENCODE_LABEL, JUNE_GEMINI_LABEL, JUNE_HERMES_LABEL, JUNE_GOAL_LABEL. Useful if you want shorter names or want to keep the label in a non-English language.
Modifier labels
These don't change the runner; they tweak how the run executes.
| Label | Effect |
|---|---|
xhigh | Force --effort xhigh (Claude) / model_reasoning_effort="xhigh" (Codex). Codex already gets this from its label; setting xhigh is the way to apply it to a Claude run. |
fast | Fast mode. Claude switches to claude-opus-4-6; Codex drops to model_reasoning_effort="medium". |
goal | Prepends /goal to the first prompt so the agent keeps working across turns until the goal is satisfied (Claude Code docs). Supported on claude, codex, and gemini. Ignored on hermes and opencode since those oneshot commands pass the prompt verbatim. Only fires on a fresh session — resumes inherit the active goal from session state. |
(xhigh and fast are read directly off the issue's labels at run time — there's no ensure-labels row for them; just add them to an issue when you want the behaviour.)
Per-runner detail
Claude Code (default)
- Binary:
claude(override withJUNE_CLAUDE_BIN). - Invocation:
claude -p <prompt> --output-format stream-json --verbose --permission-mode auto --effort <effort> [--resume <sessionId>]. - Auth:
claudeonce on the host to log in to your Anthropic account, or setANTHROPIC_API_KEYin the env that spawns june. Important: the Claude runner deletesANTHROPIC_API_KEYfrom the child env before spawning (forcing it to use the logged-in user account). See security.md → How runner auth works. - Session: Claude assigns a session id; june stores it as
claude_session_idon the issue row. Resume happens via--resume <id>. - Effort: hardcoded
maxper spawn (Claude Code doesn't persist/effort, so the orchestrator passes--effort maxevery time). Thexhighlabel forces xhigh regardless.fastswitches the model and drops effort. - Output: Stream-JSON; the final
resultevent carries the agent's reply. A "post-result grace timer" (JUNE_CLAUDE_POST_RESULT_GRACE_MS, default 30 s) catches the case where Claude prints its final answer but the CLI doesn't exit — june SIGTERMs after the grace expires and the existing reply is still treated as authoritative. - Timeout:
JUNE_RUN_TIMEOUT_MS(default0= off) sets the per-run cap for every runner;JUNE_CLAUDE_TIMEOUT_MSoverrides it for Claude specifically. See concepts.md → Timeout triage for what happens at the deadline. - Watchdog:
JUNE_CLAUDE_HEALTH_CHECK_INTERVAL_MS(default 900000 = 15 min). See concepts.md → Health watchdog.
Codex CLI (codex label)
- Binary:
codex(override withJUNE_CODEX_BIN). - Invocation:
codex exec [resume <id>] --json --skip-git-repo-check --dangerously-bypass-approvals-and-sandbox -o <last-msg-file> [-c model_reasoning_effort=<x>] [-m <model>] <prompt>. - Auth:
codex loginonce on the host. Codex stores its credentials in~/.codex/. - Effort: june forces
xhighwhenever thecodexlabel is set.fastoverrides tomedium. Other defaults come fromcodex config— the orchestrator no longer pins them from env vars. - Model: persisted in
codex config. The orchestrator no longer pins a default from env. - Session: Codex prints
thread.startedevents with athread_id; june uses that as the session id and stores it ascodex_session_id. - Output: JSONL events on stdout, plus a
--last-messagefile (-o) for robustness. june prefers the file when present.
OpenCode (opencode label)
- Binary:
opencode(override withJUNE_OPENCODE_BIN). - Invocation:
opencode run --format json --dangerously-skip-permissions [--session <id>] [-m <model>] [--agent <name>] [--variant <name>] <prompt>. - Auth:
opencode auth loginonce on the host. - Working dir:
JUNE_OPENCODE_ROOToverridesJUNE_AGENT_ROOTfor OpenCode runs only. Handy when OpenCode is sandboxed to a subdirectory. - Permissions: OpenCode reads
OPENCODE_PERMISSIONas JSON. june injects a default that deniesexternal_directoryaccess and arbitrarybash, while allowing common local inspection/test commands (pwd,ls,git status,git diff,git log,npm test,node --test). Override the whole policy withJUNE_OPENCODE_PERMISSION(a JSON string). - Session: OpenCode emits
sessionID(orsession_id) in its JSONL events; june stores it asopencode_session_id. After the run, june also callsopencode export <session-id>to harvest the final assistant message robustly — slower-streaming runs that didn't fully drain stdout still produce a usable reply. - Agent / variant / model: all persisted via
opencode config. The orchestrator no longer pins env-var defaults; thefastlabel still wins as a--variantmodifier at run time. - Empty stdout: treated as failure even on exit 0 (the verification gate posts
BLOCKED: OpenCode produced no final response despite exiting 0.).
Antigravity (gemini label)
- Binary:
agy(override withJUNE_ANTIGRAVITY_BIN). Lives at~/.local/bin/agyafter Antigravity installs. - Invocation:
agy -p <prompt> --dangerously-skip-permissions --add-dir <agentRoot>. - Auth: install the Antigravity desktop app from https://antigravity.google and sign in.
agyreuses the desktop session — no API key in env. - Model: not configurable from env. The model is whatever the desktop session is set to. june accepts
modelfor interface compatibility with the other runners but logs+ignores non-null values. - Session: none. Each Linear comment to a
gemini-labelled issue spawns a fresh process. - Replaces the deprecated
geminiCLI. Google announced deprecation for non-Enterprise tiers on 2026-06-18. The oldgemini-runner.mjsandJUNE_GEMINI_BINenv var stay in the codebase as a rollback path.
Hermes Agent (hermes label)
- Binary:
hermes(override withJUNE_HERMES_BIN). Installer drops it in~/.local/bin. - Invocation:
hermes -z <prompt> [-t <toolsets>] [-m <model> [--provider <provider>]].-zis the script-friendly entrypoint that auto-setsHERMES_YOLO_MODE=1andHERMES_ACCEPT_HOOKS=1and prints only the final response to stdout (no banner, no spinner). - Auth:
hermes initthen configure a provider/model in~/.hermes/config.toml. june supports any provider Hermes does. The orchestrator no longer pins a default provider/model from env vars; Hermes uses whatever its config selects. - Toolsets:
JUNE_HERMES_TOOLSETS(defaultterminal,web,skills,session_search,todo,code_execution). The default deliberately omits Hermes'fileandsearch_filestoolsets, which tend to loop on multi-KB orchestrator prompts. - Session: none.
hermes -zdoesn't surface a session id; each Linear comment spawns a fresh session. - Empty stdout: treated as failure even on exit 0.
Pointing at non-default bin locations
When june runs under launchd or systemd, PATH is stripped down. Runner binaries in ~/.local/bin (agy, hermes) and the OpenCode ~/.opencode/bin location are appended to PATH automatically by the respective runners — but if you have a runner installed somewhere unusual, set its bin env var:
JUNE_CLAUDE_BIN=/Users/you/.bun/bin/claude
JUNE_CODEX_BIN=/opt/homebrew/opt/codex/bin/codex
JUNE_OPENCODE_BIN=/Users/you/.opencode/bin/opencode
JUNE_ANTIGRAVITY_BIN=/Users/you/.local/bin/agy
JUNE_HERMES_BIN=/Users/you/.local/bin/hermesjun doctor prints the bin path it resolved for each runner, so you can see exactly what's about to be spawned. jun config show prints the full resolved config including the bin paths.
Adding a guard
Each runner prepends <appDir>/guards/ to its child PATH. Drop a wrapper script in guards/ to override a binary the runner child would otherwise pick up from system PATH — for example, a wrapper that refuses to run vercel from inside the orchestrator's working tree, or one that logs every git push. See guards/README.md.
Default runner
JUNE_DEFAULT_RUNNER is claude. There's no real reason to change it — Claude is the runner that must be installed for june to boot, so it's the only safe fallback. The env var exists so the codebase can express "the default" once.
Concepts
How june actually works, so you can predict what it'll do. If something here surprises you, `jun config show` and `jun logs -f` are the fastest way to see what's actually happening on your install.
Configuration reference
Every env var june reads, grouped by section. The Ctrl-F page. Source of truth: [`src/config.mjs`](../src/config.mjs).