status
One report for the whole system: per service, whether the config enables it, whether it is running (and as which pid), and the poller's progress (issue-228, decision-084).
the-loop status [--format text|json]$ the-loop status
config /home/you/.the-loop/cli-config.yaml
state /home/you/.the-loop
instance laptop-b [addressed] — 2 declared, 5 managed
service running (pid 24846) [enabled] — http://127.0.0.1:4114, healthy
gh-webhook not running [disabled]
poller running (pid 24913) [enabled]
started: 2026-08-14T20:41:12Z (2m ago)
last cycle: 2026-08-14T20:43:02Z (10s ago) — 5 item(s), 1 spawn(s), 0 comment(s) forwardedThe first two lines say which files this answer is about (issue-339, decision-119): the CLI config that was read, and the state.root resolved from it. They are there because their absence cost someone a day — status read a second, stale heartbeat under another root and reported a live poller as dead, with nothing on screen to say which file it had read. --format json carries them as configPath and stateRoot.
A conflict line appears when another candidate root also holds a poll-status.json — usually state left by a pre-13.12.0 daemon started from a different directory:
conflict /home/you/.the-loop also holds a poller heartbeat — reporting on /repo/.the-loopIt is named, never merged or deleted: moving state is destructive and yours to do. It also does not move the exit code (conflictingRoots in JSON) — whether some other directory holds an old file says nothing about whether this instance's enabled services are running.
The next line is the instance this config is (issue-322): its name (or (unnamed)), its scope mode, how many work items it declares and how many it manages in all. --format json carries the whole document as instance — the same one GET /api/v1/instance serves.
On a manager (issue-374) the fleet follows: a headline — instances hq [manager] — this instance + 3 registered, 2 of 3 live — and one row per instance, the manager's own first, as the-loop instances list prints them; the members are probed from this process, so the states are the ones the API reports. --format json carries the document as instances on every role.
An ingress running inside the service (issue-231) says so — same lock-based liveness, its lock is simply held by the service's pid — and the JSON rows carry it as "hosted": true:
$ the-loop status
service running (pid 24846) [enabled] — http://127.0.0.1:4114, healthy
gh-webhook running (hosted in the service, pid 24846) [enabled]
poller running (hosted in the service, pid 24846) [enabled]
...A repository the last cycle could not poll is named beneath that line, one degraded: per repository, with the error and whether it is retried (issue-315, decision-106). A repository whose Issues are disabled is skipped for issues only and shows the standing reason; a cycle in which no repository answered says so in words rather than leaving you to read it off 0 item(s):
$ the-loop status
poller running (pid 24913) [enabled]
started: 2026-09-02T20:41:12Z (3h ago)
last cycle: 2026-09-02T23:43:02Z (10s ago) — 24 item(s), 0 spawn(s), 1 comment(s) forwarded, 1 error(s)
degraded: octo/repo-b — listing failed, retried next cycle: issues of octo/repo-b: GitHub 502: upstream
degraded: octo/repo-m — issues are disabled on this repository; its issues are skipped and re-probed every 60 cycles, its pull requests are still polledThe exit code does not change for a degraded poller — it is running, and restarting it would degrade identically; the lines are the signal. --format json carries the same facts in the poller row's lastCycle (scopesPolled, scopesFailed, scopesSkipped).
Two properties carried over from the removed poll status (issue-191/205):
- Liveness and the pid come from the pidfile's lock, never from a file's claim. A forged or leftover heartbeat cannot make a dead poller look alive, and a stale pidfile is reported as not running — and left alone;
statusis read-only. - The heartbeat is enrichment. An absent or unreadable one loses the progress lines, never the liveness answer.
A sessions line appears when running harness sessions hold a forwarded value that differs from what the config declares now (env.sessionPassthrough, read from env.file), or hold a name a session must not hold — an env.file name or a credential, such as a token an earlier version handed the pane (issue-482) — and nothing at all when they do not (issue-410):
$ the-loop status
sessions 2 of 6 running with a stale environment — `the-loop sessions restart --all`A session's environment is frozen when its pane is forked, so rotating a credential leaves the service on the new value and every session already running on the old one. The line exists because that state is otherwise invisible: the reporter's deployment looked healthy for three days while 144 consecutive agent questions failed. Roll them with sessions restart.
Like conflict, it does not move the exit code: status's contract is "every enabled service is running", and a session on a retired credential is an observation about sessions. A host that will not report a process's environment claims no drift rather than guessing at it. --format json carries the whole comparison as sessionEnvironment, with a row per session (verdict, and the variable names that differ — never their values).
When channel deliveries are behind
$ the-loop status
channels 3 event(s) undelivered, oldest 2h ago (slack: channel_not_found) — retried every 60sA channels line appears while the bus is holding events that no channel would take (issue-409), and nothing at all when it is holding none. Each one is an event — an agent's question, a relayed reply, a phase notification — that was recorded on its work item and then refused by every subscribed channel: a rotated token, a renamed channel, a rate limit, a network blip. The poller and the webhook receiver re-post the backlog once a minute, oldest first, so the line normally clears itself; a line that stays is a channel that is still refusing, and its last error is in the line. The drain lives in those two daemons, so when neither is running the line says so instead of promising a retry that nothing will perform.
This is the other half of the same incident the sessions line reports: the deployment that lost 144 consecutive agent questions was green on every surface for three days, because the failure was one debug line in each session's own event log. Now the failed delivery is a channel.undelivered warning and a count here.
Like conflict and the environment line, it does not move the exit code: ok means every enabled service is running, and a backlog is an observation about channels. --format json carries the reading as channelDelivery (pending, oldest, waitedSeconds, lastError, channels). The queue itself is <root>/channels/undelivered.json; deleting it abandons the backlog, which is the supported way to say "do not send these".
When inbound is being split
$ the-loop status
slack-listener running (hosted in the service, pid 24846) [enabled]
inbound may be split across two Socket Mode consumers — 1/2 heartbeats reached the listener within 5s (3 of the last 8 checks short, last at 2026-09-21T07:41:02Z)
Evidence, not proof: Slack exposes no API that lists an app's connections.
Stop every other process connected with this app-level token …The listener measures its own share of the inbound traffic (issue-413): it posts read.splitCheckBeats nonce heartbeats at connect and on every reconcile, and counts how many reach it. A shortfall means a second Socket Mode consumer is holding the same app-level token, so Slack is splitting the app's envelopes between the connections and roughly half of everything inbound — mentions, button presses, slash commands — is landing elsewhere. Nothing else reports that: the envelope was never offered to this process, so there is no channel.dropped and no record of any kind.
The report reads the last eight checks, not the latest one, because at two beats a real split answers clean one time in four; the reporter who filed the issue watched the doctor read 2/3 → 1/3 → 3/3 → 1/3. It clears itself after a full clean window.
Like the environment line, it does not move the exit code: ok means every enabled service is running, and a listener hearing half of its traffic is running. --format json carries the whole reading as slackSplit.
Standing sessions get their own section, and count toward the exit code — but only the ones start would have started (the block enabled, and the entry's autoStart true). One declared without autoStart, or one that is only in the registry because you started it by hand, is reported without deciding the answer.
$ the-loop status
service running (pid 24846) [enabled] — http://127.0.0.1:4114, healthy
...
standing sessions:
supervisor running [declared]
triage not running [declared, not auto-started]The exit code is the health check (R3.3): 0 iff every enabled service is running, so the-loop status || … is the keepalive primitive. --format json emits the same facts as one document (per-service rows plus ok), including the service's URL, its /health answer, whether /mcp is exposed, and the poller's last-cycle counters.
See also
start·stop·restartevents— why something is not running.- Keeping it running —
startis not a supervisor; the unit, and the health check to watch. - Keeping it running —
startis not a supervisor; the unit, and the health check to watch.