June

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

LabelRunner CLIStateful?Good for
(none)Claude Code (claude)YesThe default. Strong long-horizon coding agent with Read/Edit/Bash. Stream-JSON output, resumable sessions.
codexCodex CLI (codex exec)YesHeavy reasoning. june forces --effort xhigh for this label. Persists its own thread/session id.
opencodeOpenCode (opencode run)YesOpen-source agent. Sandbox-friendly: ships with a default OPENCODE_PERMISSION that denies external-directory access and arbitrary bash.
geminiAntigravity CLI (agy)No (per-run)Google's successor to the deprecated gemini CLI. Model is bound to the desktop session, not env-configurable.
hermesHermes 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

  1. june reads the issue's labels (case-insensitive).
  2. The first matching runner label wins: codex > opencode > gemini > hermes.
  3. 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.
  4. 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.

LabelEffect
xhighForce --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.
fastFast mode. Claude switches to claude-opus-4-6; Codex drops to model_reasoning_effort="medium".
goalPrepends /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 with JUNE_CLAUDE_BIN).
  • Invocation: claude -p <prompt> --output-format stream-json --verbose --permission-mode auto --effort <effort> [--resume <sessionId>].
  • Auth: claude once on the host to log in to your Anthropic account, or set ANTHROPIC_API_KEY in the env that spawns june. Important: the Claude runner deletes ANTHROPIC_API_KEY from 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_id on the issue row. Resume happens via --resume <id>.
  • Effort: hardcoded max per spawn (Claude Code doesn't persist /effort, so the orchestrator passes --effort max every time). The xhigh label forces xhigh regardless. fast switches the model and drops effort.
  • Output: Stream-JSON; the final result event 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 (default 0 = off) sets the per-run cap for every runner; JUNE_CLAUDE_TIMEOUT_MS overrides 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 with JUNE_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 login once on the host. Codex stores its credentials in ~/.codex/.
  • Effort: june forces xhigh whenever the codex label is set. fast overrides to medium. Other defaults come from codex 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.started events with a thread_id; june uses that as the session id and stores it as codex_session_id.
  • Output: JSONL events on stdout, plus a --last-message file (-o) for robustness. june prefers the file when present.

OpenCode (opencode label)

  • Binary: opencode (override with JUNE_OPENCODE_BIN).
  • Invocation: opencode run --format json --dangerously-skip-permissions [--session <id>] [-m <model>] [--agent <name>] [--variant <name>] <prompt>.
  • Auth: opencode auth login once on the host.
  • Working dir: JUNE_OPENCODE_ROOT overrides JUNE_AGENT_ROOT for OpenCode runs only. Handy when OpenCode is sandboxed to a subdirectory.
  • Permissions: OpenCode reads OPENCODE_PERMISSION as JSON. june injects a default that denies external_directory access and arbitrary bash, while allowing common local inspection/test commands (pwd, ls, git status, git diff, git log, npm test, node --test). Override the whole policy with JUNE_OPENCODE_PERMISSION (a JSON string).
  • Session: OpenCode emits sessionID (or session_id) in its JSONL events; june stores it as opencode_session_id. After the run, june also calls opencode 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; the fast label still wins as a --variant modifier 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 with JUNE_ANTIGRAVITY_BIN). Lives at ~/.local/bin/agy after 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. agy reuses 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 model for 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 gemini CLI. Google announced deprecation for non-Enterprise tiers on 2026-06-18. The old gemini-runner.mjs and JUNE_GEMINI_BIN env var stay in the codebase as a rollback path.

Hermes Agent (hermes label)

  • Binary: hermes (override with JUNE_HERMES_BIN). Installer drops it in ~/.local/bin.
  • Invocation: hermes -z <prompt> [-t <toolsets>] [-m <model> [--provider <provider>]]. -z is the script-friendly entrypoint that auto-sets HERMES_YOLO_MODE=1 and HERMES_ACCEPT_HOOKS=1 and prints only the final response to stdout (no banner, no spinner).
  • Auth: hermes init then 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 (default terminal,web,skills,session_search,todo,code_execution). The default deliberately omits Hermes' file and search_files toolsets, which tend to loop on multi-KB orchestrator prompts.
  • Session: none. hermes -z doesn'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/hermes

jun 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.

On this page