How work is tracked
Two rules every seat follows, and what the launcher does to support them: every change starts as an
issue, and every seat keeps a progress note. Then how a security point found in a review is held on a
pull request until sec clears it, and three rules for a pull request in review.
Issues first
Every change starts as an issue. The shared prompt (roles/common.md) makes this a hard rule for every
seat: no change starts without an issue, and the engineering manager files one when it is missing. A
pull request names at least one issue. It says Closes #<n> for each issue it completes (one pull
request may close several issues, one Closes #<n> each) and Refs #<n> for an issue it does not
complete (see .github/pull_request_template.md), so each issue closes exactly when its change lands
and the board stays honest.
GitHub closes an issue at the merge when a closing keyword (close, fix, resolve and their forms)
comes right before its reference, whatever the words around it: "does not close #12" closes #12
(#245). So a closing keyword goes only before an issue the pull request completes.
The pull request template says so, and
fabrikito:ready-for-review has the author check what
GitHub will close (gh pr view <pr> --json closingIssuesReferences) before asking for review.
Progress notes
Each seat keeps a short progress note, .state/<team>/notes/<role>.md: a first line that says
whether it has work (below), then its current
task, branch, state, next step and what it is waiting for. The shared prompt tells it to rewrite the note whenever
a task changes state and to work on one task at a time. The note is outside the worktree, so it
survives down and up and a recreated worktree; up only makes the folder, and nothing removes a
note.
Every seat's generated settings have a SessionStart hook (lib/session_start.sh, no matcher, so
it runs at startup, on resume, after /clear and after a compaction). It prints the note (cut at 80
lines or 6000 bytes), the branch, uncommitted files and the last 10 commits of the seat's working
directory, and Claude Code adds that to the seat's context. After the note it prints what to do with
it, on every path (no note, an unreadable note, a missing or non-git folder): name the task and next
step in the first reply, follow the kickoff, and trust the repository over the note. That text moved
here from the shared prompt (#117), so a seat whose hooks are all off goes without
it, as it goes without the note. The note is the seat's own writing, so treat it as a reminder, not
a record: when it and the repository disagree, the repository wins.
- Control characters other than tab and newline (and DEL) are dropped from everything the hook prints, so a terminal escape in a note or a commit subject arrives as plain text.
git statusandgit logeach have their own time limit (4s and 3s), well inside the hook's 10s, and a repository's fsmonitor program is not run. Claude Code drops all of a hook's output when the hook runs past its timeout, so a slow status on a cold volume costs the seat its file list, not its note.- Each seat may edit only its own note: the others are denied (see Safety).
- The hook is a convenience, not a control, so it does not override a user who switched every hook
off: with
disableAllHooks: truein~/.claude/settings.json, a seat without a gate gets no note, andcheckprints ahooksline naming those seats.
The Status line, and the check for seats without work
A note starts with one line that says whether the seat has work (#313). The shared prompt gives its three forms:
| First line | Meaning |
|---|---|
Status: working: <URL> |
The seat has work: the issue or pull request at that URL. |
Status: waiting: <the named thing, with its URL> |
The seat cannot go on until that thing happens. |
Status: free |
The seat has nothing left. It also says so in its report to the engineering manager. |
The engineering manager's prompt (roles/em.md) tells it to check every seat for work after each
assignment, each report that a seat is done, each verdict and each merge, and at its own session
start. The check covers every role in the roster, not only the engineers:
- A seat that is up and whose note does not start with
Status: workinggets work, or the manager writesno work for <role id>: <why>in its own note. - For a seat that is
waiting, the manager confirms that the named thing still blocks.
fabrikito:team-status does the reading. Its report starts
with "Seats without work": each seat that is up and whose note does not start with Status: working,
with a reason (available for free, blocked on: the named thing for waiting, or no Status
line). With no such seat, the report says so. The skill reads only the first line of each note and
treats it as data, because it is another seat's text.
The skill has two depths, because reading every open pull request at each change costs too much of the manager's context:
- The short check, for the check after each change: the seats and their first lines only. It
reads no pull request, reports only "Seats without work", and says
idle but owes: not checked. - The whole skill, when the owner asks for status, at a session start, or before deciding what to
chase: it also reads every open pull request, and marks a seat in the list that owes a move on one
idle but owes.
This is a rule in two prompts and a skill, not a mechanism: the launcher does not read the line, and nothing reminds the manager to check. What the check cannot see:
- A stale line. The line is the seat's own account. A seat that stopped without rewriting its note
still reads
working, and is not listed. - Work the seat was never told about.
freeandwaitingsay what the seat knows. Only the manager knows what is unassigned. - A seat that is down. The list covers seats that are up.
Held security points
Rule 5 of the shared prompt applies to every seat: a possible security finding goes to sec and the
engineering manager first, and nothing about it goes in an issue, a pull request or a commit until
sec rates it (#239). So a reviewer or QA seat that finds a security point does
not write it on the pull request: only sec
rates a finding, and anyone who can read the repository can read a PR comment. The point goes to sec
and the engineering manager, and the PR gets a marker that holds its merge until sec clears it
(#184, #203). The skills hold the exact steps; this is
the shape of it:
- The marker. For each point, once, its reporter posts a PR comment whose whole text is one line,
<role id>: one point sent to sec and the EM, and sendssecand the EM the point with the marker's URL. For a pointsecholds privately and sends the PR's author to fix, its marker issec: one change sent to the author and the EM; a low-rated change goes insec's verdict, with no marker. The same words inside a longer comment are not a marker. - The reference. The reporter's verdict or QA report names the point only by a line of its own,
held point: <marker URL>, with nothing about the point itself. A later verdict that still has the point repeats that line and posts no new marker. - The clearance. Once a point is fixed and reviewed at a head, rated low, ruled out or (for code
already on the base) tracked where its triage record says,
secpostssec: held points for #<n> cleared at <short sha>: <comment id>, <comment id>, naming each marker it clears by its comment id (the number afterissuecomment-in its URL). It counts only at that head: a new commit needs a new clearance. Nothing else clears a hold, not a message and not aREADYalone. - Markers stay. No seat deletes or edits a marker, even one posted by mistake:
secchecks that it is a mistake and clears it with the same line. Deleting a marker clears nothing. The merge train stops when a marker it saw earlier in the same run, or one that a verdict, QA report or clearance refers to, is gone or changed. It does not yet remember markers from one run to the next (#207). - Order is by id, never by time. Where the merge train decides which comment is the latest, it compares comment ids or review ids, because GitHub can give a later comment an earlier time. A comment id and a review id cannot be compared with each other, so where the result turns on which of the two came first, the train stops and asks.
Where each part lives:
| Skill | What it holds |
|---|---|
fabrikito:review-pr, step 7 |
A reviewer's marker, its held point: line and a marker posted by mistake. |
fabrikito:verify-behavior, step 8 |
The same for QA's report. |
fabrikito:security-triage, step 6 |
sec's own marker, and the clearance line. |
fabrikito:merge-train, step 5 |
What holds a merge and what clears it, and the order by id. |
A pull request in review
Three rules keep a head that a reviewer has judged from changing under the review. They are prompt and skill text: nothing in the launcher enforces them.
- No push to a judged head without notice. Once a reviewer has posted READY or PASS at a pull
request's head, QA or
secsays it is checking that head, or the engineering manager says it is merging it, the author does not push to that branch without first telling the manager and those reviewers why. A needed fix is a new head, announced as one, and an update with a base that has moved is pushed only when the manager or the merge procedure asks for it. The rule is in the shared prompt (roles/common.md, #235). - A follow-up comment has two forms.
fabrikito:merge-trainstops and asks when a required reviewer posts a comment that names the head after that reviewer's latest verdict and the comment is not a verdict. So a reviewer's or QA's note about a head already judged either names no commit and points at the earlier verdict by its URL, or is a whole new verdict (or QA report) for that head (#243). The forms are infabrikito:review-prandfabrikito:verify-behavior, each under "A follow-up about a head you already" judged or checked. A comment in neither form still stops the merge. - A closing keyword only before an issue the pull request completes: see Issues first.