Repository navigation
The Project board reads decisions from the Project folder only, so a decision archived in the Session folder reads as "decision file gone" #1809
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: f8187c1a5d4046ad9ac678cfeac77d1f
Branch: dev/1-project-board-decisions-union
Base: 84de666
At: 2026-10-30T07:05:05Z
Symptom
A user holds a meeting in a room that belongs to a Project and closes it (decide). The decision file is written — decisions/<date>-<slug>.md exists on disk — but the Project board's Decisions section stays empty and the same meeting, under Meetings, is drawn with decision file gone. Nothing was deleted: the file is in the Session's working folder, and the board only looks in the Project's own folder.
Root cause (read side; the write side is the documented behaviour)
meeting decidewrites throughcore/src/host/meetings.js:277const dir = args.dir ?? workDirFor(rt, args.exec);workDirFor(core/src/host/project-state.js:99-105) is the Session cwd →workDirOf({botId, roomId})→teamFolder(). That is "the Session's working folder", which is what the docs and#1767say a decision is archived in. This issue does not move it.- The board resolves a folder of its own —
core/src/host/project-board.js:72folderOf(project):project.workDir, else a member room's or Bot'sworkDir, else''— and reads only that folder (:93listProjectState(dir)). Its own comment at:69-71says this is "the same folder a meeting resolves throughworkDirForbefore it archives a decision, so the reader and the writer look in one place"; that sentence is the assumption this issue disproves. - So the two agree only by accident (Project folder == Session folder). In a default install the Project names no folder at all (
project-createwrites none), the Session archives in the team folder, and the board answersno working folder: this Project names none(project-board.js:90). - Second, independent half: even when the two folders are the same,
listProjectStaterefuses a folder that is not a git repository (core/src/host/project-state.js:60-61,repositoryAtat:29), so the board answersnot a repository: <dir>and lists nothing. A decision file is the board's own artifact; requiring.gitto display it is what makes the default install empty. - Client side: a decided meeting whose named file is not in the list gets
path: ''and is rendered asdecision file gone(core/src/client/project-board.js:212), which reads as "someone deleted it".
Reproduction (real host, no sleeps)
Probe: core/test/probe-p216-decisions-union.test.js (scratch probe, not for the PR; the PR test is the same cases in core/test/project-board.test.js).
Run on a clean 30c04907 tree:
DSH_HARNESS=/home/xuke/.local/lib/node_modules/@deepseek-ai/dsh TMPDIR=/tmp TZ=UTC \
node --import ./test/harness-resolve.mjs --test test/probe-p216-decisions-union.test.js
Every case is driven through the real meeting tool (open → three position → decide) and then rt.projectBoard('project-p1'). Red on untouched main — 4 fail, 1 pass (case E is the guard that must keep passing):
Case A — the Project names folder A, the room names folder B, the deciding Session works in B:
# PROBE A recorded decisionFile: decisions/2026-10-30-where-do-the-decisions-of-p1-live.md | decisionDir: undefined
# PROBE A Project folder decisions/: absent | Session folder decisions/: holds decisions/2026-10-30-where-do-the-decisions-of-p1-live.md
# PROBE A board.decisions.dir: /tmp/ds-p216a-project-QWV4tY | reason: "not a repository: /tmp/ds-p216a-project-QWV4tY"
# PROBE A board.decisions.files: [] | decided: [ [ 'meeting-cce50e35e1', '' ] ]
not ok 1 - probe P-216 A: the Project names folder A, the member names folder B - the decision stays in B and the board reads it
error: |-
the meeting records the folder it archived in
+ actual - expected
+ undefined
- '/tmp/ds-p216a-room-0Xlb6U'
Case B — default install: Project, room and Bot name no folder; the Session works in the team folder:
# PROBE B project workDir: undefined | decisionDir: undefined
# PROBE B team folder decisions/: holds decisions/2026-10-30-which-folder-holds-the-record.md
# PROBE B board.decisions.dir: null | reason: "no working folder: this Project names none"
# PROBE B board.decisions.files: [] | decided: [ [ 'meeting-62e6327552', '' ] ]
not ok 2 - probe P-216 B: default install (no folder named anywhere) - the board lists the team-folder decision
error: |-
the meeting records the team folder it archived in
+ actual - expected
+ undefined
- '/tmp/ds-p216b-team-RTnZRk'
Case C (delete the file for real → still decision file gone), case D (two folder-less Projects sharing the team folder do not list each other's decision) and case E (project_state still answers not a repository: <dir>; the one case green before any fix) are the same run — C and D red for the same listing reason, E green.
By hand, in the running app: Settings → Projects → create a Project (set its folder or leave it empty) → in a room bound to that Project run a meeting and decide → open the Project board: Decisions is empty, the meeting shows decision file gone, while the file exists in the Session's working folder (the team folder by default).
Expected
- The decision file stays where
decidearchived it — the Session's working folder. Nothing is moved, so an existing install's files keep their place. - The board lists that file in Decisions and links the meeting to it (
pathnon-empty), with nodecision file gone. - This holds when the Project names one folder and the deciding member/Session works in another.
- A file that really was deleted still shows
decision file gone(existing#1767case stays green). - A folder shared by two Projects that name none still shows each Project only its own decisions (isolation by the file a meeting names, not by the folder).
- The
project_statetool keeps its.gitgate and itsnot a repositoryanswers; only the board's own read is ungated.
Minimal fix (verified on 30c04907)
Three host files, no client change, no new endpoint, no new storage, no write-side move:
core/src/host/meetings.js— indecide, record the folder beside the name:decisionDir: dirin thedecidedrecord (closed, next todecisionFileat:286-293). One line;workDirForand the write itself are unchanged.core/src/host/project-board.js—decisionsOfreads two sources and returns their union: the Project folder (folderOfuntouched, still read whole, so a file put there by hand stays listed) and, for each decided meeting of this Project, the folder in itsdecisionDir— from a meeting folder only the file that meeting names is listed. That is what keeps two folder-less Projects sharing the team folder apart. Files already listed are de-duplicated by path; a record with nodecisionDir(written before this change) resolves through the Project folder exactly as today.core/src/host/project-state.js—listProjectState(dir, { repository = true } = {}): the gate isrepository && !repositoryAt(dir). Theproject_statetool andreadProjectStatekeep the default; onlyproject-boardpasses{ repository: false }.
Verified in a scratch tree (source restored after the run; repository untouched): probe 5/5 pass — case A # PROBE A board.decisions.files: [ 'decisions/2026-10-30-where-do-the-decisions-of-p1-live.md' ], reason: ""; case B # PROBE B board.decisions.dir: null | reason: "" with the file listed; case C after delete files: [] and path: ''; case D P1 and P2 each list only their own file; case E unchanged not a repository: <dir>. Related suites project-board + project-board-view + project-board-entry + project-state + projects + project-settings-page + project-owner-gate + project-assistant + meetings + meetings-decide-paths + meetings-persist = 99/99, and the whole core suite 2145 tests / 2139 pass / 6 fail — the same six environment failures as untouched main (5 × Cannot find module 'jsdom', 1 × check-arch-docs empty diff passes).
Residual, not in this slice: two decided meetings in different folders that name the same relative path (decisions/<same>.md) de-duplicate to one listed row. Nothing in the record is lost; the rows are one per path by design (the client's row key is the path).
Scope
core/src/host/meetings.js,core/src/host/project-board.js,core/src/host/project-state.js— about 45 lines added or changed in total.core/test/project-board.test.js— the same cases as end-to-end tests: Project folder ≠ Session folder, default install (no folder named anywhere), real delete stilldecision file gone, two folder-less Projects in one folder, and the unchangednot a repositoryanswer ofproject_state.patches/ds-bot/files/src/host/{project-state,project-board,meetings}.jsare verbatim mirrors of files upstream does not carry, sonode scripts/ds-bot-patches.mjs generatemust run after the source edit (checkthen provescore/=ds-bot/+patches/ds-bot/). No patch is hand-edited. Regeneration note: any core change rewrites the whole patch chain, and#1805is regenerating the same chain; land this after#1805, or backfill the patches this slice does not carry with main's bytes (the approach#1805used). Open at the then-currentmainas base.- No client change (the board already renders whatever files it is handed) and no docs change: the write location is still "the Session's working folder", so the existing sentence stays true.
Acceptance
- The file is still archived in the Session's working folder (its location does not change), and the test asserts that.
- Default install: the board lists the decision,
meeting.pathnon-empty, nodecision file gone. - The same holds when the member folder differs from the Project folder.
- A file that is really deleted still reads
decision file gone(the#1767deletion case stays green). - Two folder-less Projects sharing one team folder: the other Project's decision never appears on this board.
project_statestill answersnot a repository: <dir>for a plain folder (its answers are byte-for-byte unchanged); onlyproject-boardreads without.git.
Duplicate check (GitHub, open and closed, 2026-10-30 ≈05:3xZ)
decision file project board 22 hits, none the same topic (#1767/#1772 are this feature, #1774 is "a decision never reached disk"); decisions folder workDir board 2 hits = #1767/#1772; meeting decision file not listed 2 hits, same two. No duplicate.
Activity
HugoXK commentedon Oct 30, 2026
Claim
Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: f8187c1a5d4046ad9ac678cfeac77d1f
Branch: dev/1-project-board-decisions-union
Base: 84de666
At: 2026-10-30T07:05:05Z
The record is the
Claim-Idline in this comment and in the issue body; the label and assignee could not be written by our account, so they are absent by permission, not by intent.HugoXK commentedon Oct 30, 2026
pickup-ok — assigned to FlyBot Dev-1, branch
dev/1-project-board-decisions-union, base = the then-currentmain(live now3f8cbb25c42316e84d3d21f5f55e0fe80c8b0a98; theBase:line in the claim comment says84de6663from the day it was filed).Slot: this is the third PR in our single push queue (
#1801→#1804→ this →#1788→#1810), because a whole-chaingeneraterewrites every patch face and two PRs in flight would overwrite each other's shape. Until its slot opens it stays local: no build, no generate, no push.Work already done locally at that base, for the record: red first re-taken on
3f8cbb25(three cases red — Athe record names the folder it archived in, Bthe team-folder decision is listed, Dthe other Project's decision is never listed here), rebased with no manual conflict (#1770did not touchdecisionsOf/archivedOf), 11 related suites 110/110,core2172/2172.The label and assignee could not be written by our account (permission), so they stay empty by capability, not by intent.