Operating

The board

One GitHub project board is the only tracker. An agent writes two fields, a job on a runner computes every other field, and status changes only when a merged pull request closes an issue.

Updated

stable

Prompt 1

Learn how one board stays true

Reads this page, explains the two-written-fields model back to you, and proves the iteration trap against the live GitHub schema. Writes nothing.

Prompt 2

Measure your tracker, then derive what a human types

Reads your real tracker through its API, finds every field typed twice or typed at all, and proposes a derive-on-diff reconciler. Writes only after you confirm.

This page comes from the fleet's own component document for board. We call it stable because every script addresses the board through one constant. Five separate files bind that constant, and every pull request checks it again. That constant is the only part of a hosted product a repository can pin.

If work is not on the board, it does not exist.

A body of work is a Feature issue with ordered sub-issues. Its status comes from a merged pull request whose body closes the issue, and nobody ever types a status. If the board is wrong, the fleet works on the wrong thing. We keep no second list to check it against, on purpose, and this component exists to stop anyone from creating one.

The vocabulary (plan, Feature, issue) and the lane routing that decides which agent serves a row are covered on How work flows. This page covers the mechanism underneath. It explains which fields an agent writes, which ones a job computes, and the four ways the board has ended up showing something false.

The fleet owns no file that is the board

The component's list of owned files is empty. That is accurate, not something we forgot to fill in. The board is a hosted product. Everything in the repository that touches it belongs to some other component, and exactly three parts split the work. One script owns the path that writes to the board. One set of workflows owns the checks that keep it accurate. The scheduler owns the only thing that reads the Todo status.

Because we name that boundary explicitly, no fourth component can quietly start keeping its own copy of the board.

An agent writes two fields. Everything else is derived

An agent's whole job on the board is to assign itself and give the issue a title. It can also add a priority label if it wants. A reconciliation workflow on a hosted runner computes every other field, and writes a field only when its value has changed.

FieldDerived from
Lanethe login of the primary assignee. The lane names the box, never the portfolio
Prioritythe priority label, or the middle value if there is no label
Sprintthe current iteration if the field is empty. The reconciler never overwrites an iteration someone chose on purpose
Statusthe board's own built-in workflows. The reconciler only acts as the backstop that moves closed issues to done
Archivedan issue that is closed and done comes off the board. An issue that is open and archived goes back onto it
Week / Cycleno script ever writes this field. See below

This design has two useful results:

  • Agents make zero field calls. However busy the agents get, they cannot cause a rate-limit outage against the tracker from their side.
  • Because the reconciler writes only when a value changed, it is idempotent. Running it twice has the same effect as running it once, so it is safe to run on a timer and on an event.

The shape of a filing is a real choice

--chain              task k is blocked by task k-1
                     → exactly ONE issue is ever dispatchable

--wave               every task in a group is blocked by the WHOLE of the previous group,
                     and by no sibling
                     → a whole group starts at once

Someone once designed a plan as five parallel waves and filed it as a chain fourteen issues deep. Nothing reported an error. The board just had one issue ready to dispatch instead of five, and the fleet ran at a fifth of the parallel capacity the plan had, until someone looked at the dependency graph.

If two phases can run at the same time, file them as a wave. The filing tool now refuses a long chain when its tasks share no file, and it prints the same plan rewritten as waves. A serial order needs a stated reason. It is not the default.

The iteration field is read-only to every script, and this one is worth stealing

Any API write to a board's iteration configuration creates new iteration ids, and every assignment that pointed at the old ids is left pointing at nothing. On this board, a single call lost 84 field assignments.

The schema shows why, and the documentation does not, so reproduce it yourself instead of trusting this paragraph:

gh api graphql -f query='{ a: __type(name:"ProjectV2IterationFieldIterationInput"){name}
                           e: __type(name:"ProjectV2Iteration"){kind inputFields{name}} }'

a comes back null, which means the input type a caller would need does not exist. And e.inputFields has no id. The full input for an iteration is a start date, a duration and a title. So you cannot say "update this one". You can only say "here is the new list".

The field id is durable, and it is the only id worth hardcoding. An iteration id changes as soon as a human edits the field in the interface. For that reason the filing script looks up the current iteration live every time it files an issue, and treats the result as advisory.

The rule that applies elsewhere is this. Before you write to a hosted API, check through introspection whether the API keeps the identity of the thing you are updating. If its input type has no id field, you are not updating it. You are replacing it.

Read the board, never recall it

Every count on the board changes by the minute, so this page tells you how to read each number instead of stating it.

You wantRun
Open issuesgh api repos/<org>/<repo>/issues --jq 'length'
Progress for every Feature, in one callbash _infra/scripts/feature-progress.sh
One issue with its commentsgh api repos/<org>/<repo>/issues/<n>
One line for each queued issue, with the reason it is heldbash _infra/scripts/sweep-dispatch.sh --explain

That table has two traps. gh issue view currently fails against this board with a deprecation error from the older projects API, so use the gh api form, which works. Also, gh api prints its errors to standard output, not standard error. Check the shape of what you captured. Do not only check that it is non-empty.

Four ways this board has been made to lie

  • Saying you are not closing an issue closes it. GitHub matches a closing keyword followed directly by an issue reference anywhere in a pull request body or commit message, and it ignores the words around it. A sentence like "this does NOT close #1234" counts as a closing reference. This happened twice in one day on one issue. The first time, it came from a pull request whose whole purpose was to keep the issue open. The second time, it came from the follow-up pull request that documented the trap, because that pull request quoted the sentence in its own body.

    Recovery takes two steps, and only the first one is obvious. A close also sets the status to done, and reopening the issue does not reset it. The issue ends up open and done, and no selector reads that state. So reopen the issue, and set the status back, then read both values back to confirm. To mention an issue without acting on it, use a form with no keyword, and use a placeholder number in any example. That is why the example above uses #1234.

  • The final state arrives eventually, not instantly. The close event tries to archive the issue, and a scheduled sweep guarantees it. In one measured window, concurrency cancellation killed 25 of 30 event runs in a row. Expect a few rows at any moment that are closed but still listed. This fleet has already raised a false alarm by filing "done is stuck at zero" after reading the board once.

  • A Feature never closes itself. A parent issue has no pull request, so a closing keyword can never fire on it. Instead, the agent that finishes the last child issue attests the Feature, and a separate sweep closes it. If a Feature is complete but has no attestation, the sweep flags it and does not close it. That is the right way to fail, because the alternative is a parent that closes itself while its own verifier objects.

  • Nothing gates the default branch. On this plan tier, branch protection and rulesets both return 403 in every repository. A red check is information. A merge never waits on a human. The full argument is on How work flows.

On this page