Skip to main content

crewship inbox

The inbox is the unified “things needing a human” feed — waitpoints awaiting approval, escalations from agents, failed runs, and direct messages. The CLI is the scriptable mirror of the /inbox web page; cron + jq + inbox list is the canonical “ping Slack when unread piles up” recipe. Defined in cmd/crewship/cmd_inbox.go.
Auth: every subcommand requires crewship login plus an active workspace. The inbox is backed by the inbox_items table (migration v85), written-through by waitpoint creation, escalation creation, and run-failure handlers.

Subcommands

Flags

inbox list

inbox resolve

Which kinds it applies to:
  • message, failed_run and the other inbox-owned kinds — always.
  • waitpoint, escalation — only when the backing record is already gone (a token the sweep pruned, a staged hire already swept, a keeper-synthetic escalation that never had an escalations row). A live one is refused with 409: this is the inbox-side resolve only, it does not call the source endpoint, and closing the row here would leave the run parked forever waiting for a decision that never reached it.
On the refusal the CLI exits non-zero and prints the command that decides it at the source, with the real token filled in:
An escalation gets the same shape with crewship escalation resolve <id> --action approve|reject. To know in advance which case a row is, read source_missing on inbox get.

inbox act

answer posts the text as your comment on the issue and delivers it to the same agent session that raised the card; the run resumes from its checkpoint with the answer as its next delivery (one delivery, one run — if the session is mid-run the delivery queues until that run ends). take_over is the same operation as crewship issue work <id> --action take_over (#2448): it transfers the issue to you — live runs get a cooperative cancel and a hard stop, waiting assignments, open plan tasks and parked routine runs are cancelled, pending mentions are superseded, work_mode becomes human with you as the worker, work_revision is bumped and the issue moves to TODO — and settles the session from awaiting_input to idle. dismiss only settles the session. Every action leaves an inbox_acted receipt on the issue’s event log (crewship issue events <id>) naming who acted, the action, the session’s agent_version and, for an answer, the delivery and run it produced, and resolves the card in place with the same receipt under payload.receipt. Acting twice is refused (409 already acted on); waitpoints and escalations are decided through their own commands, not here.

inbox bulk read

inbox bulk resolve

--ids and --all-unread are mutually exclusive on both bulk subcommands.

Examples

Triage

Output is coloured by state (yellow=unread, cyan=read, green=resolved) and kind (yellow=waitpoint, red=escalation/failed_run, cyan=message), so a 50-row feed scans at a glance.

Polling the badge

inbox count hits GET /api/v1/inbox/count (the bell-badge endpoint) — much cheaper than inbox list when you only need the integer.

Mark an item

inbox get <id> (backed by GET /api/v1/inbox/{id}) is the detail counterpart of list — it returns the markdown body and the structured payload that the list view omits. inbox archive <id> maps to a resolved PATCH with resolved_action=archived; only non-decision kinds (messages, failed-run notices, advisories) archive — the server 409s an archive on a source-managed waitpoint/escalation whose source is still live. read and unread are per user: the server computes state for the caller from inbox_item_reads, so marking a role-targeted item read clears it from your feed and nobody else’s, and unread only drops your own marker. resolve and archive are workspace-shared — one decision closes the row for everyone. inbox list and inbox get print the state the server computed for you; the CLI never assumes it locally. On a waitpoint or escalation, inbox get also reports whether the record that would decide it still exists — source_missing in --format json, and one line in the human view before the body:
false means a pending waitpoint token, a PENDING_REVIEW hire or a pending approval still backs the row, and inbox resolve will be refused; true means the source was pruned and the row may be dismissed from the inbox. The field is present only on get (never on list) and only for those two kinds; absent means the server did not probe, not that the source is live. --format json on either list or get also carries the §12 attention contract where a producer has adopted it (work package B10): thread_key (the server-side merge key — two producers describing the same recurring condition collapse onto one row instead of the client stitching them together), attention_class (decision | input | review | repair), and actions ([{"id", "label", "effect", "irreversible"}, ...], unioned across every producer that merged into the row). All three are omitted for a kind that has not adopted the contract yet — script against payload for those, same as before. On a CREDENTIAL escalation, inbox get also prints whether resolving it needs a second approver, and which of the two controls demands it:
The same answer rides the JSON as second_approver_required / second_approver_by_workspace / second_approver_by_tier / security_level_label, the fields the crew escalations list carries. It is computed when you read the row, not when the escalation was raised: the workspace toggle and the credential’s tier both change afterwards. Absent means the server said nothing, not that a single approver will do — see Keeper → The switch is a floor, not a master switch. resolve is the inbox-side only — it does not call the source endpoint. To actually approve a waitpoint through to the executor, use crewship routine waitpoints approve <token> (crewship approvals approve is a different queue — approval requests, apr_… ids — and does not decide waitpoints). To resolve an escalation lifecycle, use crewship escalation resolve <id>. The inbox row tracks the decision; the source endpoint enacts it.

Bulk operations

Bulk operations submit the whole id set to the server-side POST /api/v1/inbox/bulk endpoint (one request flips many rows), chunked at 500 ids per call — the endpoint’s id cap. On a resolve the server skips decision items (source-managed waitpoints/escalations and blocking rows) rather than closing them. The final line summarises the aggregated counts:
A stale or already-resolved id lands in not found / skipped; it does not fail the command (the process exits 0). Only a transport error or a non-2xx bulk response exits non-zero. If --all-unread returns exactly the page cap (500), the CLI refuses to proceed and changes nothing, telling you to re-run after the batch or narrow with --ids, so the tail isn’t lost.

Sources

The inbox is human-attention-scoped: it holds the things somebody has to look at, not every event that happened. Where those items are delivered outward — email, webhook, chat, push — is crewship notifychannel.

Common errors

  • either --ids <csv> or --all-unread is required — bulk command needs one of them.
  • --ids and --all-unread are mutually exclusive — pick one.
  • --ids parsed to empty list (got "") — comma-string was blank after trimming.
  • hit the 500 unread-item page cap; no items were changed … pass --ids to target a specific subset — --all-unread returned a full page and can’t tell whether more remain, so it changes nothing. Re-run after clearing this batch, or target explicit --ids.

See also