-
Notifications
You must be signed in to change notification settings - Fork 0
The Lane Board
Describes robot-council/core v0.7.1.
The lane board at {web prefix}/dashboard/lanes groups the fleet's lanes by repository. Above the lanes it shows one meter per repository: that repository's open-issue count, and how far it has moved since the morning. This page covers the meters: what the number is, where it comes from, what "count unreadable" means, and how an operator sets up the GitHub App that supplies it. What a lane row shows (state, watcher, occupancy against capacity, On what) is in Presence and The Task Lifecycle. Since v0.7.0 a working lane lists every task it holds, each with its sub-label where it has one (#409).
-
The count is open issues, with pull requests left out. It is GitHub search's
total_countforrepo:OWNER/NAME is:issue is:open(GitHubApp.php). -
The line under it compares that count with 08:00 today, in
robot-council.dashboard.timezone. It reads up N, down N, flat, or no baseline today. A scheduled command takes each repository's baseline once local time passes 08:00, from the latest reading in the stale window before 08:00, which is 07:00 to 08:00 by default. With no reading in that window, no baseline is taken, and the meter reads no baseline today all day (TakeBacklogBaselineCommand.php, #339). Only up is colored, because up is the bad direction. -
A reading goes stale after
robot-council.backlog.stale_after_minutes, 60 by default. Past that, the meter reads count unreadable rather than showing the last number it had (Backlog.php).
Core fetches it, every five minutes, through a GitHub App the deployment holds the key for (#383). It is the one thing core reads from GitHub, and it is display only. Nothing that frees a lane, places work, or changes a task depends on it; those learn from the webhook alone. That boundary is why this is an exception to the rule that core reads nothing from GitHub, and why it goes no further (CLAUDE.md).
Each run (BacklogFetcher.php):
- reads the repositories the lane board currently shows, at most 25 a run, those tried longest ago first;
- finds the App's installation on each repository's owner, once per owner per run;
- mints one short-lived token per installation, narrowed to Issues read, and caches it, encrypted with the application key, until five minutes before it expires;
- asks search for each repository's count, and stores one reading per repository.
The fetch is the last of the package's scheduled entries. It runs in the background and never overlaps itself, so a slow GitHub cannot hold back the coordination checks. A rate limit, or a second request in a row with no answer, ends the run early; the repositories it did not reach are tried first next time.
A session can still report a count itself with backlog_report. That is how the meters were filled before #383, and it still works, but no session needs to.
A failed fetch stores no reading, never a zero. Each cause below leaves the meter unreadable once the last reading, fetched or reported by a session, is older than the stale window:
| Cause | What doctor's backlog fetch check says |
|---|---|
| No App is configured | Passes. Nothing is fetched, and the meters show only what sessions report. |
| Only one of the two variables is set, the App ID is not numeric, or the key does not parse |
key unusable. Fails, and so does the github app check. |
| The App is not installed on the repository's owner |
no installation. Fails. |
| The installation does not include this repository, and it is private |
refused, typically with 422. Fails. |
| GitHub refused the token or the search |
refused, with the status. Fails. |
| GitHub answered, but with no usable installation, token, or count |
unparseable, with the status. Fails. |
| GitHub did not answer, rate-limited the run, or returned an incomplete search | Undetermined. These clear on their own. |
| Something outside GitHub failed, such as the cache store |
error. Undetermined. The exception's class is in the log, not in doctor's output. |
| The scheduler is not running | Undetermined, because no board repository has been fetched recently. |
php artisan robot-council:doctor runs both checks: github app (configured, and does the key parse) and backlog fetch (per owner, is there an installation; per repository, how did its latest fetch go). Neither check calls GitHub, and neither prints a credential (GitHubAppDiagnosis.php).
Do this once, as an owner of the organization that will own the App.
-
Open the new-App form for that organization:
https://github.com/organizations/ORG/settings/apps/new. -
Name and homepage. Any unique name. The homepage URL is shown publicly and GitHub never calls it, so point it at the repository rather than at your deployment. Leave everything under Identifying and authorizing users and Post installation empty.
-
Webhook: untick Active. The App needs no webhook. The fleet's own webhook is separate and stays as it is.
-
Permissions: Repository permissions > Issues: Read-only. GitHub sets Metadata to read-only itself. Grant nothing else.
-
Where can it be installed? Choose Any account if the board shows repositories from more than one organization or account. A public App can be installed by anyone on their own account, but that gives them nothing from your deployment, which only asks about the owners of repositories on its own board. Only on this account is enough when every repository belongs to the owning organization.
-
Create it, and note the App ID. It is a number shown under About, not the client ID that starts with
Iv. -
Generate a private key. GitHub downloads a
.pemfile. Keep it out of chat, tickets, and the repository. -
Install the App on each organization whose repositories appear on the board, from the App's Install App page, choosing Only select repositories. Prefer that to All repositories: an allowlisted developer can name any repository as a session's repository, and the board then shows that repository's count to every allowlisted developer. A private repository left out reads count unreadable, because GitHub refuses the search. A public one may still be counted.
-
Give the deployment both values, as secrets where the host supports them:
Variable Value ROBOT_COUNCIL_GITHUB_APP_IDthe App ID ROBOT_COUNCIL_GITHUB_APP_PRIVATE_KEYthe key, base64-encoded onto one line: base64 < key.pem | tr -d '\n'A PEM pasted whole is also accepted, including one with its newlines written as
\n, in the PKCS#1 form GitHub downloads or PKCS#8. -
Redeploy. On Laravel Cloud a new or changed secret takes effect only after a redeploy.
-
Check it: run
php artisan robot-council:backlog-fetchonce rather than waiting for the schedule, thenphp artisan robot-council:doctor --only="github app,backlog fetch". Both checks should pass, and the board's meters show counts.
Later changes:
- A new organization on the board: install the App there (step 8). Nothing on the deployment changes.
- A new repository in an installed organization: add it to that installation's selected repositories.
-
Rotating the key: generate a new key, replace
ROBOT_COUNCIL_GITHUB_APP_PRIVATE_KEY, redeploy, check with doctor, then delete the old key on GitHub. -
Turning the fetch off: set
robot-council.schedule.backlog_fetchtofalse. Otherwise, with no App configured, it does nothing.
A host running Laravel Telescope with its HTTP client watcher should add 'token' to Telescope::hideResponseParameters(). The response to minting a token carries the token in its token field, and Telescope would otherwise store it in the clear.
- Counts are GitHub search's, which answers from an index. A count can trail an issue opened seconds ago. A search GitHub marks incomplete is not recorded at all.
-
Token shape: GitHub's installation tokens changed shape, and v0.6.10 and v0.6.11 refused every current token as
unparseable (HTTP 201). v0.6.12 accepts the current shape (#395).