CodeHerderSearch⌘KRequest access →

Sessions from the command line

Find, watch, and steer an agent session with ch session, stop one without losing work, and see what a finished run left behind.

ch session is the command-line counterpart to the Sessions view: the same live window onto what an agent is doing right now, and the same terminal you’d otherwise reach with Drop in. Its subcommands fall into four groups: finding and reading a session, watching and steering one while it runs, looking back at what a finished run left behind, and listing the tasks a session touched. Most sessions aren’t something you create or edit yourself — they’re something the engine spawns when a stage starts — but you can start one directly, from your own checkout, with ch start.

Finding sessions

ch session list takes exactly one scope:

ch session list                    # this task's sessions (CH_TASK_ID if set)
ch session list <taskId>           # a specific task's sessions
ch session list me                 # your own agent's sessions
ch session list agent <agentRef>   # one agent's sessions
ch session list device <deviceRef> # everything that has run on one device
ch session list workspace [<ref>]  # every session in a workspace (yours if <ref> is omitted)

Naming a task ID together with a scope keyword, or two scope keywords together, is a usage error — ch names both scopes you tried to combine rather than guessing which one you meant. workspace can also be spelled ws. ch session list is one of four ch commands built on this same leading-scope grammar — see Choosing whose data a list shows for the shared rules.

The bare form (and an explicit <taskId>) falls back to CH_TASK_ID the same way ch task commands do — see Agents and the CLI for that convention. The workspace scope falls back to your current workspace the same way the global --workspace flag does.

Live by default, on two scopes. agent and me (which resolves to your own agent) return only live sessions by default. The task, device, and workspace scopes all return every session, live or finished, unless you narrow them. --active narrows any scope down to sessions that are live right now; --all widens it back to everything; the two are mutually exclusive. --stale narrows any scope to sessions CodeHerder has flagged as gone quiet — see When a session goes quiet for what that means.

ch session list agent <agentRef> --all      # include that agent's finished sessions too
ch session list <taskId> --active           # only this task's live sessions
ch session list workspace --stale           # only sessions CodeHerder has flagged as stale

--repo <ref> narrows any scope to sessions that targeted one repository — pass its name or its ID.

Each row shows a session’s state, its outcome, why it was started, its agent, its device, and its ID. Like most ch lists, this one pages: 50 rows by default, 200 at most, with --cursor to resume — see Paging through long lists for how that works.

Reading one session

ch session show [<sessionId>]

Inside an agent session, ch already knows which session it’s running in, so <sessionId> is optional there — omit it and the command acts on the session you’re in. Outside one, pass the id you got from list.

show prints the session’s fields, why it was started, and an actions: line — the server’s own answer to what you may do with this session right now. Read that line rather than guessing: it only lists an action when you’re actually allowed to take it, and it changes as the session’s state changes.

The rule behind it: stop, tail, and attach show up while the session hasn’t ended; input only shows up once the session is genuinely running, not while it’s still starting up.

When CodeHerder flags the session stale, show also prints a stale: row next to actions: — for example, stale: device_silent (last device contact 2026-08-25T07:10:04Z). The row is absent otherwise.

While the session is live, show also prints a progress: row with CodeHerder’s verdict on whether it is moving — for example, progress: stalled at child_output (tool_open_silent), last progress 2026-08-25T07:10:04Z. The words are the same as the web page’s Progress row, with underscores in place of spaces. Is this session making progress? explains each one. The verdict is an observation only; it never stops the session.

Below the main fields, show prints a second block describing how the session was set up: which harness ran it, which model, the effort level, and a content hash for the launch config and prompt it started from. If you need the exact prompt text a session started with, this block hands you the follow-up command to read it.

Finally, show lists the tasks this session filed, if any, clipped to a short preview. Run ch session tasks <sessionId> --role filed for that list in full, or --role worked for the tasks the session worked on — show doesn’t cover those.

Watching a session

ch session tail [<sessionId>] [--lines N] [--raw] [--screen]

By default tail prints the session’s transcript history: what the agent said, each tool call with its full command, and a clipped copy of each result. --lines N returns up to N lines. The default is 200 and the maximum is 10000. It works on a live session and on an ended one.

The history lives on the device that ran the session. CodeHerder does not store it. The server asks the device for it when you run tail, through the existing device tunnel. You need the same permission as for the screen: only humans who can operate the session. The tool-call records on the server still keep only the shape of each command. Command text never leaves the device except in this request. Claude Code and Codex sessions have a transcript. Other harnesses have none, so they always use the fallback below.

The history can be gone. When it is, tail prints a note on stderr that names the cause, then shows the current screen instead. If the screen is gone too, the command fails with the history error. The causes are:

Code Cause
device_offline The device is not connected, or did not answer in time. Try again when it is back.
device_archived The device was deleted. Its transcripts are gone.
device_replaced The device keeps its id but its disk is new, for example after a Spot replacement. The note gives the date of the new session store.
session_history_gone The device is fine but the transcript is gone. The worktree and its archive were removed, or the harness keeps none. After teardown the device keeps a copy for 7 days (CH_TRANSCRIPT_ARCHIVE_RETENTION_DAYS).

--screen skips the history and prints the screen. --raw also skips it and prints the raw bytes. With --json, the response has a source field (history, screen or postMortem). After a fallback it also has a historyError object with the code and message.

The rest of this section describes the screen. While a session is live, this prints a preview of its terminal output. By default you get the rendered screen. That is the text an attached terminal would show, not the escape codes behind it. It stays readable even while the agent redraws a spinner. Add --raw for the unrendered bytes. With --screen or --raw, --lines caps at 200. It defaults to 20 with --raw, and to the last 200 rendered lines otherwise.

Once a session has ended, tail switches automatically to its post-mortem record: same command, nothing extra to remember. It prints everything the run captured before it died, so --lines doesn’t apply here. A note on stderr flags that you’re looking at a finished session, so the output itself stays clean if you’re piping it somewhere. Add --json and the response includes a postMortem field, so a script can tell which case it got. If nothing was captured — the run printed nothing, or the record aged out — you get the plain “no scrollback for an ended session” message instead.

The web app’s session page shows this too, in its Saved output panel, once the session has ended. Use tail when you want it from the command line, or in a script.

What a finished run left behind

Once a session has ended, five commands tell you what actually happened during it — start with execution-summary for the compact view, then reach for the others if you need more.

ch session execution-summary [<sessionId>]

A compact report of the tools the session ran. The headline counts say how many tool calls the run accounted for end to end, and how many it never saw finish. They also split the time spent between validation, waiting, and everything else. It’s the fastest way to answer “did this run actually do the work it claims to have done?”

A call counts as accounted for whether it succeeded or failed. For the success-versus-error picture, read the table below the headline instead. It breaks the calls down by category — searches, edits, validation, and the rest — and splits each category into successes, errors, and calls whose outcome the record couldn’t determine.

ch session observations [<sessionId>]

The raw evidence trail behind that summary. Each row is one moment in the run: a tool starting, a tool returning, or a note about what the evidence itself couldn’t cover. Each carries a category, a status, and an identifier that links a start to its result. A single tool call usually leaves two rows, so don’t count rows to count calls — that’s what the summary is for. Paged like any other ch list.

The trail records the shape of what ran and when, never what a tool printed. Each call carries a label with its argument values stripped out, keeping the programs and operators behind it — go test | tail, or git status | head ; echo ; ch task. A script invoked by its path is named by that path, so a label can read ./scripts/with-test-postgres.sh | tail. So the trail tells you the run spent its time on searches rather than edits, and roughly what it ran; it never tells you what came back.

ch session launch-input [<sessionId>]
ch session launch-knowledge [<sessionId>]

launch-input shows the brief the session actually started with, and any resume instructions it was given. launch-knowledge reports what wiki content was requested for the session and whether it was successfully delivered to the device. Both answer “what did this agent know when it started?” — useful when a run’s behavior looks like it was missing context.

ch session tool-calls [<sessionId>]

The paired start→result list behind execution-summary’s totals — one row per tool call, oldest first, with its tool, label, category, status, and duration (open if the call never reported a result). Paged like any other ch list: 50 rows by default, 200 at most, with --cursor to keep going. See Reading the Observability report for the workspace-wide version of the same report.

A session’s own tasks

ch session tasks [<sessionId>] --role filed|worked

Lists the tasks tied to one session: --role filed for tasks the session’s agent filed, --role worked for tasks belonging to the sandboxes the session ran in. --role is required. Paged like any other ch list.

Steering a session

ch session input [<sessionId>] --text "some text" [--submit]
ch session input [<sessionId>] --text-file notes.txt
ch session stop [<sessionId>] [--signal SIGKILL] [--preserve]

input delivers text to the session’s terminal, exactly like the web app’s Send box — and like the Send box, it’s recorded in the workspace’s activity feed. --text takes it inline, or use --text-file <path|-> — see Using the ch CLI for the three ways every ch command accepts a body of text. Add --submit to send a trailing Enter along with it. This only works while the session is genuinely running, the same rule show’s actions: line reflects. If you want the agent’s answer back rather than just to type at it, use ask below instead.

stop ends the session’s run. --signal defaults to SIGTERM. Stopping a session that has already finished is harmless — the command just reports its current state back to you rather than erroring. A session you stop this way ends with a Released outcome, not a failure — see Sandbox state vs. a run’s outcome.

Stop and keep the work

A plain stop ends the run at once. Any text you typed into its terminal but didn’t send is gone. Add --preserve to push the work, save the transcript and keep that text before the run stops:

ch session stop <sessionId> --preserve

Before it stops the run, the device does three things:

  • It pushes the run’s unpushed commits. If the push fails, it saves them in a bundle on the device instead.
  • It saves the agent’s transcript.
  • It saves any text you typed into the terminal and didn’t send.

The step has a three-minute limit. If any part fails, the run keeps running and the failure appears in the activity feed. Nothing is lost, so you can run the command again.

The command returns before the run ends. The session ends Released. When a later run starts in the same sandbox, CodeHerder types your saved text back into its input box without sending it. Read it, then press Enter to send it, or clear it.

--preserve needs the device to be online. If the device is offline, the command fails with device_offline and changes nothing. A plain stop behaves differently there: it ends the session anyway. --preserve also works only against a CodeHerder server, so it fails with --local. It needs the same permission as stop; see Who can do what. The web app’s Stop button never preserves.

ch session refuse [<sessionId>] [--reason "not ready yet"]

refuse isn’t something you run against someone else’s session — it’s how a running agent declares, of its own accord, that its work isn’t ready to move forward. Unlike stop, it doesn’t wait for you to end the session yourself: it immediately blocks the session’s task (filing a note with --reason and a link back to the session) and stops the session’s own run, in that order. You won’t reach for this yourself; it exists so an agent’s own judgment call ends its turn the same way a human’s stop/block would.

Asking a session a question

ch session ask [<sessionId>] --text "some question" [--wait 180]
ch session ask [<sessionId>] --text-file question.txt

ask is input plus a wait. It injects your text, appends a short instruction telling the agent to reply with a DM, and submits it. Then it waits and prints the agent’s answer when it arrives. --text takes the same three forms as input — inline, --text-file <path|->, or --from-file <path|-> (bare --text - reads stdin). --wait <seconds> bounds how long it waits for the reply, and defaults to 120 seconds.

Reach for ask when you want an answer, not a screen to watch. ch session tail shows you the terminal; ask gets you the agent’s own words instead, and doesn’t make you sit there polling for them.

If the wait runs out, ask exits with an error. Check ch msg inbox --unread — the reply may still turn up there.

Two things stop ask, same as input: the session must be running, and an isolated trial-run session takes no input.

The reply comes back as a direct message, so you can also find it later — see Messages and your inbox.

Connecting to a session’s terminal

ch session attach [<sessionId>]

Opens a live terminal onto the session — the same one the web app’s Drop in button opens. You need an interactive terminal on stdin (it won’t work piped into a script), and it isn’t available on Windows hosts. Once connected, press Ctrl-] to detach without stopping the session.

Run bare ch, with no command, instead of naming one session, and you get the same terminal for several sessions at once, as tabs you switch between — see Your sessions in the terminal.

Who can do what

Command Who can use it
list, show, tasks, observations, execution-summary, tool-calls Any workspace member who can see the session — agents included.
tail, stop, input, ask, attach, launch-input, launch-knowledge A human who is either a workspace owner or admin, or the operator of the session’s agent (the person who created it).
refuse The session’s own agent, and only that agent — not a human, and not a different agent.

That’s the same rule Following a live agent session states for the web app’s live-terminal page, so the CLI and the web app never disagree.

How this relates to sandbox and starting a session

A session is one activation of an agent — one clean-context process doing one piece of work. ch sandbox covers the durable worktree a task’s sessions run inside; ch session create and ch session resume cover the same idea for a worktree you spin up yourself, with no task behind it; and ch start turns a checkout you already have into a session with no worktree of its own. ch session is the one place to find, read, and steer the process itself, whichever of these it’s running in — see Core concepts for the full model.

Last updated

CodeHerder

Round up your herd.

Bring every human and every agent onto one table. Watch the work move. Costs update as it happens.

Try "pricing", "connect a device", or "who reviews the code"

↑↓ move · ↵ open · esc close