What the First Message Says
Every fresh session, whether new, manual or remote, begins with the same kind of first message: the setup prompt Coop persisted on the assignment row. Read it whole, because every clause is load-bearing.
- A label line: the task title and the project, or "No project".
- The marker line:
cwkCoop session setup <marker>. - Who asked, and for which project, and on which Mac when it is not office.
- What not to do: "Do not start work, claim Coop items, run tools, or send messages."
- What to do: reply READY and wait, because an admission request comes next, then the task or a notice saying why it could not come.
And what it does not carry: no task body and no credentials. The title is a label for Dad's eyes. The work itself arrives later, after a claim has been committed, and this chat has claimed nothing yet.
The text lives in the row's setup_prompt column, so the engine always knows the exact words a chat was sent. Rows created by the first installed version predate that column, and Assignments.prompt rebuilds their original wording rather than today's: "Editing setup copy must never strand an in-flight launch." Discovery searches for what was actually sent, so rewording the prompt must never change what an older launch is looking for.
Finding a Chat Nobody Named
Coop usually cannot know a new chat's id in advance. Claude Code in background mode picks its own UUID and ignores --session-id; a composer chat gets its id when someone presses Send. So the chat has to be found, and the marker is how. It is a fresh uuid4 per assignment, stored in a UNIQUE column.
Discovery lists the live chats of the assignment's vendor that fall inside a time bound taken from the launch, reads only each candidate's first human message (for Codex, the thread's stored first user message; for Claude, the first non-meta user event in its transcript) and searches it for this launch's marker line. Three rules bound the search:
- The vendor must match. A Codex launch is never satisfied by a Claude chat.
- A time bound from the launch, measured per vendor. A Claude chat must have started no earlier than two seconds before the launch, by its registry's
startedAt. A Codex thread only has to have been active since then, not created: Codex keeps an unsent new-chat draft and reuses it for the next launch, so a setup was once sent in a thread created minutes before its own launch (#315) and a creation bound never found it. Widening the bound admits nothing stale, because the marker did not exist before its launch. The Codex query also reads only the 100 most recently updated threads, so it searches the recent end of the list, not all of it. - More than one match is an error, not a guess. "More than one session received this setup. Cancel and choose the intended existing session."
A Codex chat started by Coop's own bootstrap must additionally be the exact thread named in the bootstrap's ready receipt.
Two Incidents That Shaped the Pattern
Byte-exact matching (ee7493f, 2026-09-29). Discovery used to compare the whole prompt. Three of five new-session assignments expired while their chats sat at READY: Dad added instructions before pressing Send, the Codex composer reflowed the setup into paragraphs, and a Claude chat that opened in a scratch workspace prefixed its first message with a notice. Every one of those chats was the right one. Now discovery looks only for the marker line, with whitespace between its words allowed to change and the UUID kept exact.
The folder filter (d056235, 2026-09-30). Claude Desktop 2.1.284 opens a claude://code/new?folder= chat in a scratch workspace; its registry cwd follows the project only later. Discovery filtered candidates by that cwd and never reached the marker check, so todo #280's chat answered READY and sat idle until Dad told it to proceed by hand. Coop found it ten minutes late, after the work was already done, and #271 had expired the same way the day before. The commit's title is the rule: cwd is data, not identity. A new chat is no longer filtered by folder at all, and the work envelope names the project directory, so a worker that started somewhere else moves there first.
Fifteen Minutes, Then a Clear No
The setup window is SETUP_TIMEOUT = 900 seconds. Inside it, a native hiccup (an app restarting, a locked metadata store, a vanished socket) keeps the request waiting and shows "Retrying: …", while an engine verdict (the work changed, two receivers matched, the item is already dispatched) fails it at once. If no receiver is confirmed in fifteen minutes, the assignment expires with "No receiver confirmed within 15 minutes. The work was not claimed." and Dad hears about it.