Concepts
One session per issue
Section titled “One session per issue”Every Linear issue gets its own agent session. The first webhook for a fresh issue spawns a session; every later comment resumes the same session via the runner’s --resume (or runner-equivalent) mechanism. Session ids live in SQLite (data/agent.sqlite) keyed by issue id.
Per-runner session ids are stored in separate columns. Switching the runner label between turns starts a fresh session for the new runner — you don’t get a half-Claude, half-Codex conversation by accident.
The state machine
Section titled “The state machine”The orchestrator listens for webhooks on active-state issues. The mapping:
| Linear state | What June does |
|---|---|
| Backlog | Hard-ignored. No DB write, no run, no state change. Move out of Backlog first. |
| Todo / any unstarted | First webhook → moves to In Progress + spawns the session. |
| In Progress / any started | Comment → resumes the session. |
| Ready for Review | Comment → resumes the session (a human nudge wakes the agent). |
| Blocked | Comment → resumes the session. |
| Done / Canceled | Comment → resumes the session. |
| Waiting | Comment → resumes (acts like Ready for Review). |
On exit, the orchestrator transitions the issue:
- Clean exit →
Ready for Review. - Non-zero exit or
is_error→Blockedwith the failure summary as the comment.
Steering an in-flight run
Section titled “Steering an in-flight run”A new comment arriving while a run is active steers it. The orchestrator:
- Queues the new comment.
- Sends
SIGTERM(thenSIGKILLafter 5s) to the running process group. - Restarts the run, resuming the same session, with the queued comment as the next user message.
The steered run posts no final comment and does not transition state — the issue stays in In Progress until the flush run completes. Use this to redirect mid-task without waiting for the current turn to finish. Consecutive steers are capped at LINEAR_AGENT_COMMENT_MAX_AUTO_FLUSHES.
Debouncing burst comments
Section titled “Debouncing burst comments”Comments arriving in quick succession with no active run are collapsed into one run. The window is LINEAR_AGENT_COMMENT_DEBOUNCE_MS (default 30s). The agent sees all queued bodies in the trigger block of its prompt, so you can type three follow-ups in a row without spawning three runs.
Progress comments vs final stdout
Section titled “Progress comments vs final stdout”Two distinct outputs:
- Progress comments stream in while the run is active. The runner posts them via the Linear API directly — “Edited X.” “Tests pass.” “Hit blocker Y.” Blunt, factual, no narrative voice. The cadence guideline is ~3–10 per long run; zero is often correct on short tasks.
- Final stdout is the runner’s reply at exit. The orchestrator captures it and posts it as the issue’s final comment. Audits, answers, decisions, deliverables — that’s stdout.
Progress comments are top-level by default. Set LINEAR_CLAUDE_THREAD_REPLIES=1 to thread them under the triggering comment instead.
linear-action directives
Section titled “linear-action directives”The runner can ask the orchestrator to mutate Linear on its behalf by emitting a fenced JSON block:
```linear-action{"action":"issueCreate","title":"Wire metrics dashboard","description":"…","teamKey":"JUN"}```Supported actions: issueCreate (with title required; optional description, teamKey, teamId, labelIds, priority, stateId). The orchestrator extracts the block, executes it, strips it from the comment body, and appends a one-line execution summary so the action is auditable in the thread.
Timeout triage
Section titled “Timeout triage”claudeTimeoutMs (env LINEAR_CLAUDE_TIMEOUT_MS, default 0 = no timeout) bounds each primary run. When a positive value is set and a run blows past it, the orchestrator SIGTERMs the process and spawns a short triage pass — a second runner with Read,Glob,Grep,Bash only and a 5-minute budget — to inspect the working tree against the issue and the dying run’s stdout tail. Triage emits VERDICT: RESUMABLE or VERDICT: BLOCKED:
- RESUMABLE → issue moves to
Ready for Reviewwith the triage summary. A human comment can wake the agent to pick up. - BLOCKED → issue moves to
Blockedwith the triage summary as the explanation.
Set LINEAR_CLAUDE_TRIAGE_ENABLED=0 to fall back to the old “exit 143 → Blocked” path.
Waiting sweep
Section titled “Waiting sweep”A periodic sweeper ages stale Ready for Review issues into Waiting when the human has gone quiet. Each tick (LINEAR_CLAUDE_WAITING_SWEEP_INTERVAL_MS, default 5 min) it scans issues in LINEAR_CLAUDE_WAITING_FROM_STATES (default Ready for Review) and moves any whose latest comment is from the bot and older than LINEAR_CLAUDE_WAITING_AFTER_MS (default 1 h) into LINEAR_CLAUDE_WAITING_STATES[0] (default Waiting).
Waiting is a completed-type state, so a fresh human comment wakes the agent like any other parked state. Disable with LINEAR_CLAUDE_WAITING_SWEEP_ENABLED=0.
Inline image attachments
Section titled “Inline image attachments”When the runner’s reply contains a markdown image reference pointing at a local absolute path under the agent root (e.g. ), June uploads the file to Linear via fileUpload and rewrites the markdown to the returned assetUrl before posting the comment. Supported extensions: .png, .jpg/.jpeg, .gif, .webp, .svg. Remote URLs and relative paths are left untouched. Files outside the agent root are skipped for safety.
Self-replies are filtered
Section titled “Self-replies are filtered”The orchestrator filters its own bot comments out of the trigger path, so it never loops on its own output. Webhook updates that don’t represent a routing event (subscriber-only changes, etc.) are also ignored.