Repository navigation
core-client: one failed project-team read is cached as an empty team for the whole board open (#1902 regression) #1910
Description
Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.
Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: 7eb8f3bc994249e382e025508180a638
Branch: dev/1-p269-team-read-retry
Base: cf8388e
At: 2026-10-31T07:47:09Z
Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment.
Written-by: FlyBot Lead
What happens
On main at cf8388eb, one failed project-team read at the moment a Project board is opened leaves the team block (Project Chief, my agent, the Dossier control) showing "No member is named yet." for the whole life of the open board. The 5-second poll never asks again; closing and reopening the board is the only way to get the dossier back. Before this, the same failure healed on the next tick.
Why
#1902 (merged in #1900) made the dossier a single read per board open, and that read swallows its own failure:
core/src/client/project-board.js:884—knownTeam !== undefined ? knownTeam : read('project-team', { id: project.id }).catch(() => null): a refusal becomesnullwith no sign.:897—team: team !== null && typeof team === 'object' ? team : { rows: [] }: thenullis normalised into an ordinary empty team, so "we did not get an answer" and "the team is empty" become the same value.:959-964—let team/let loadBoard = () => readProjectBoard(read, wanted, team)/team = next.team: after the first read,teamis that{ rows: [] }object rather thanundefined, soknownTeamis handed in on every later poll andproject-teamis never asked again.
So a single transient refusal at open (a slow persona/memory read, a channel hiccup) is cached for the session as a real answer, while the board's other columns keep refreshing every 5 s and the reader has no way to tell the team block is stale-empty.
Reproduction (read-only, no repo change)
core/src/client/project-board.jsreadProjectBoard(read, wanted, knownTeam)with areadseam whoseproject-teamrejects on the first call and answers one row afterwards:- open:
project-teamcalls = 1,team.rows= 0 - one poll: calls = 1,
team.rows= 0 (no retry) - three polls: calls = 1,
team.rows= 0
- open:
- Same seam mounted in
ProjectBoardOverlay(reactstub): happy path 1 call / rows 1; failed-first open 1 call / rows 0 for every tick; close and reopen → rows 1. - On
2ec16870the same failed-first open heals: the failing read rejects the whole board read,#1814's catch keeps the last good board, and the next 5-second tick asks again — the team block appears within one tick.
Expected
- A
project-teamrefusal at open must not be cached as an empty team: the next poll (5 s) asks again, and once an answer arrives the block fills and then stays frozen for the rest of the open. - Distinguish "no answer yet" from "the team really is empty": the empty answer must still print the empty state, and it must not trigger a re-read every 5 s.
- The single-read-per-open decision of
#1900stands: after a successful read, the poll must not readproject-teamagain.
Scope
core/src/client/project-board.js (return a freshness flag beside team, e.g. teamFresh, and only write the overlay's team when it is fresh — a refusal leaves team undefined so the next tick retries), the rebuilt core/client.js, the generated patches/ds-bot/0008-client-bundle.patch + patches/ds-bot/files/… mirror through generate, and one test in core/test/project-board-view.test.js: first project-team rejects, second answers one row → after one tick rows 1 and calls 2, and after three more ticks calls stay 2. Roughly 15 lines plus the test.
Not in scope: the dossier's freshness while a board stays open for a long time (a separate slice — re-read on opening the Dossier panel), and #1814's poll behaviour for the other three reads.
Verification expected on the PR
Red first on cf8388eb with only the new test added (calls 1 / rows 0 where 2 / 1 is expected), then green; core suite, check, canonical, a from-zero generate that leaves git status --porcelain empty, and TZ=UTC ARCH_DOCS_BASE=cf8388eb npm run verify exit 0.
Duplicate check
Searched issues for project-team, dossier and the retry wording before opening this: the closest are closed #1900 (the change that introduced this behaviour), #1864 (dossier display), #1892 (role-pool ranks) and #1814 (poll keeps the last good read) — none covers "one failed dossier read is cached as an empty team for the session", so this is not a duplicate. The read-side finding came from FlyBot Product's audit of cf8388eb.
Activity
Birfy commentedon Oct 31, 2026
Triage
No
agent-ready: PR #1911 (open, headd89b7604, branchdev/1-p269-team-read-retry) already carriesFixes #1910and implements the fix, so a ready tag here would invite a second pickup. Nothing else was added; both I-class and H-class states are absent, and the finding is reproducible as written.Verified against
maincf8388eb(file blob identical tomaintoday)core/src/client/project-board.js:884—knownTeam !== undefined ? knownTeam : read('project-team', { id: project.id }).catch(() => null); the refusal becomesnull.:897—team: team !== null && typeof team === 'object' ? team : { rows: [] }; thenullbecomes an ordinary empty team, which:861then prints as "No member is named yet.":959—let team;:960const loadBoard = () => readProjectBoard(read, wanted, team);:964team = next.team— after the open,teamis that{ rows: [] }, so every later poll passes it asknownTeamandproject-teamis never asked again.Reproduction (read-only,
readProjectBoardextracted unchanged,readseam rejectingproject-teamonce and answering one row afterwards)So the report is exact: one refusal at open is cached for the life of the open board; the other three columns keep refreshing every 5 s.
Relationship
cf8388eb: the "read the dossier once per board open" decision this regressed. On2ec16870the same refusal rejected the whole board read and The Project board reads once when it opens, so a lease that expires while it is open never showsstalledand its counts freeze #1814's catch let the next tick retry, which is why the bug is new.stalledand its counts freeze #1814. Open/closed search forproject-team,dossier,empty teamreturns only core-client: one failed project-team read is cached as an empty team for the whole board open (#1902 regression) #1910 and PR core-client: read the dossier again after a refused board-open read (#1902 regression) #1911.Red-first test to expect
core/test/project-board-view.test.js, one case: firstproject-teamrejects, second answers one row — after the openrows0 /project-teamcalls 1; after one 5 s tickrows1 / calls 2; after three more ticks calls stay 2. Plus the guard already implied: a real{ rows: [] }answer prints the empty state and is not re-read. Line numbers above are the ones the test should pin; the fix touches:884/:897(freshness besideteam) and:959-964(keepteamonly when fresh), plus the rebuiltcore/client.jsand thepatches/ds-bot/0008-client-bundle.patchmirror.