How dispatch works¶
Dispatching (enter, or c for the custom modal) immediately self-assigns the issue and moves it to In Progress. Issues with unresolved blocking relations park as ⛓ blocked in the Queued column and start automatically when their blockers complete — rechecked every minute and whenever a colinear task finishes; f force-starts one, converting its blockers into merge-order dependencies that hold the PR in draft instead. Then per issue:
- Worktree off
<remote>/<defaultBranch>under the repo'sworktreeRoot. If the task has a pinned or open PR, the worktree checks out that PR's branch instead, and an existing worktree already holding the branch is reused. A checkout that has been deleted since — by you, byrm -rf, by a disk cleanup — is rebuilt. - Triage pass (read-only): picks the repo the work belongs in (from the allowlist descriptions) — which is what
repo: autoin custom dispatch (c) leaves to it, and the right answer for a set of sub-issues spread across repositories. Auto is the default there, and it keeps triage on, because triage is the thing that does the picking. Until it routes, the worktree is cut in the first repo on the allowlist and the card says so, a JSON verdictdo/too_big/needs_info, and a verification tier —local-light(lints + unit tests),ci(agent confirms the CI config covers the change, then pushes the draft PR early and lets GitHub carry the heavy suites), orneeds-env(contended local envs: verify what's possible, list the rest in the PR body).too_big/needs_infoland in Needs Input for you to decide. Fortoo_bigthe agent also returns a split plan — single-repo sub-issues with dependencies; entering the task lands in plan review:spaceto drop items,Ato create them as sub-issues (withblocksrelations),Dto create and dispatch (later,uon the parent card dispatches its sub-issues via a picker).cposts the breakdown to the tracker as a comment instead;rrequeues once you've acted.Dat dispatch time (or the modal's triage field) skips triage entirely; re-dispatch keeps a successful triage plan unless you ask for a redo. - Work pass: every session opens with a full task-context block (issue + description, parent, repo/remotes, all PRs with pin markers, triage plan, your instructions, merge-order dependencies). Existing PRs are adopted — the agent reviews their diff and pushes to their branch, never duplicates. A subtask checklist file drives the card's progress bar; lints and tests per the verification tier; subagent diff review; push to the repo's
pushRemote(your fork, if configured); draft PR againstprBaseof the upstream viagh pr create --draft. Agents may stack PRs (chained by base branch, rendered as a stack). Agents never mark PRs ready — that's yourd. - Checks from the repo config, then PR polling: state, CI rollup, review decision, mergeability. Matching prefers open/merged PRs over closed duplicates, and
mcan pin the canonical PR (number,#123, or URL); a failed task that gains a live PR un-fails itself. WithciAutofix, red checks dispatch a fix session that pulls the failing logs and pushes a fix.
Label dispatch¶
"autoDispatchLabels": { "CLOUD": "agent", "SAS": ["colinear", "bot"] }
A tracker label becomes the dispatch button, per team: the minute sweep picks up open issues in a listed team carrying that team's label and enqueues them through the normal pipeline — triage included, which is the safety net for anything labelled optimistically.
The map is the scope. A team with no entry is opted out entirely, and each team names its own label
because labels aren't namespaced — agent meaning "dispatch this" in CLOUD says nothing about what
it means in SAS, and one team's label on another team's issue does nothing.
The guards are the same purity rule the sub-issue sweep uses. Tracker state decides eligibility (backlog / unstarted / triage only), so an issue whose finished task was dropped by retention can never be resurrected by its label. An issue assigned to someone else is never taken — auto-dispatch self-assigns, and that would be theft. Three per sweep across all teams, so bulk-labelling lands as a trickle.
Removing the label stops future dispatch and cancels nothing already running.
A worktree that went missing¶
Delete a worktree with anything other than git worktree remove and git keeps its registration:
git worktree list still names the path and the branch, and adds prunable at the end. Nothing
acts on that unless something prunes.
That stale entry used to be what colinear found when it asked where a branch was checked out, so the
next r started an agent in a directory that no longer existed — and a deliberate git worktree
add at the same path is refused outright while the registration stands:
fatal: '…' is a missing but already registered worktree;
use 'add -f' to override, or 'prune' or 'remove' to clear
The lookup now clears what it finds and cuts the checkout again from the branch, which is untouched by any of it. Every commit comes back. Anything uncommitted does not — it only ever existed in that directory — so the card says so rather than carrying on as if nothing happened:
the worktree was gone — recreated it from justin/clo-213-… at 4f1c9ab.
Committed work is back; anything uncommitted was not.
The conversation survives too. Claude Code files transcripts against the working directory path,
not the directory, so once the checkout is back at the same path r resumes the same session — the
agent picks up where it left off, and the line above is its warning that some of what it remembers
doing is no longer on disk.
Until you press r, the other doors say the same thing rather than misbehaving: s and S refuse
with "its worktree is gone", and :diff says so instead of reporting an empty
diff — which would have read as "this branch changed nothing".
A git worktree locked checkout is left alone even when its directory is missing, because prune
leaves it alone and colinear should not second-guess a lock.
Manual dispatch — a worktree, no agent¶
The custom modal's start field has a second setting: manual — worktree only. The issue is
assigned and moved to In Progress and the worktree is cut off <remote>/<defaultBranch> exactly as
usual, and then nothing runs. The card sits in Working with ⏸ worktree ready — r starts it, no
spinner, no session, no tokens.
This is for the work that goes badly when an agent starts from a blank checkout: a design doc whose
headings are the contract, a file layout you want followed, a first commit that fixes the shape of
the thing. Write it in the worktree (s opens a shell there), then r hands the same worktree to
an agent — ensureWorktree reuses what's there, so the agent starts from your skeleton rather than
inventing its own.
x cancels a prepared task the way it cancels a queued one; the worktree stays for :gc.
Talking to a running agent¶
M on a card (board or :tasks) opens a paragraph-sized box and sends the agent a message without
attaching to it — "use the existing helper", "don't touch the schema", a pasted stack trace. enter
is a newline; ctrl+d sends (and wakes a parked agent), ctrl+q queues it without waking.
- A live agent takes it at its next turn boundary. It can't interrupt a command already running, so a message sent during a four-minute test run lands when that finishes; usually it's seconds. The session is a streaming conversation, so your message arrives as a user turn — the agent answers it and carries on.
- An idle task — PR open, done, failed, interrupted, needs-input — is woken: colinear starts a session (resuming the same transcript, so the work is still in context) whose opening prompt carries your message. Sending is the whole gesture; you don't have to remember to press
rafterwards.ctrl+qsends without waking if you'd rather it wait. - Parked work stays parked. A
blockedtask keeps the message but is not started — a message is not a reason to jump a dependency,fis — and neither is atrackingparent or a task already queued. Their message rides into the session they were going to have anyway.
| task is | your message |
|---|---|
| working / triage / checks / rebasing | pushed into the live session, read at the next turn |
| pr open, done, failed, interrupted, needs input | queued, and a session starts to read it |
tracking parent (with coordination on) |
queued, and a coordinator session starts to act on it |
| blocked, tracking (experiment off), already queued | queued for the session it was already going to have |
Messages land in the activity log either way, so the transcript shows what you told it and when. Waking doesn't touch the tracker — it queues the task the same way r does, so no state moves and nothing is posted.
If a session dies between accepting a message and acting on it (an abort, a crash), the message goes back on the task rather than vanishing. That can occasionally repeat one the agent had already read: a duplicate paragraph is a cheaper mistake than a silently dropped instruction.
Sessions: attach, chat, background¶
s on any task hands your terminal to claude --resume in its worktree (a live agent is suspended first — never two writers in one worktree). Chat, steer, do work. Quit claude (/exit) and colinear asks "resume agent in the background? [Y/n]" — enter sends the conversation, including your interactive stint, back to a headless agent and returns you to the board. S opens a plain shell in the worktree without touching the agent.
Restart and in-flight tasks return as interrupted (r resumes the same transcript); planner chats resume their sessions too. Sessions are cwd-keyed by Claude Code, but colinear pins every session to its worktree path, so you can run coli from anywhere.
r restarts work that stopped with nobody driving it — interrupted, error, escalated, needs_input, blocked — plus a manually dispatched task waiting to be handed over, and a tracking parent when coordination is on. On anything else it says why there was nothing to do (the PR is open, the agent is live, the card is done) rather than reporting a requeue it didn't make.