Skip to content

meetings: a decision is never written to disk, so a restart forgets it and a second decide doubles the card and the file #1774

Closed

Description

@HugoXK

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: 2d12e71e775c46b9a4ffd186b3c1ec2e
Branch: dev/3-meeting-persist
Base: 84385f2
At: 2026-10-30T04:34:48Z

What happens

A meeting is never written to disk. core/src/host/meetings.js mutates store() in memory and returns; the module contains no save() call at all, so every state transition is lost when the process restarts.

Reproduced on main 84385f2faa63a033cc9f7e5f69f5225bf7ca1408 with the real host and the real meeting tool, no failure injected:

  1. open + one position → memory has 1 meeting, the on-disk state.json has 0; after a restart the meeting is gone entirely.
  2. open + two positions + decide → the decision file is written and one card is created, but state.json still says status: "deliberating". After a restart meeting read starts with meeting-… [deliberating], and a second decide succeeds: the card count goes 1 → 2 and decisions/ gains a second …-2.md.

Root cause

store()[id] = meeting (:165, open), store()[meeting.id] = { ...meeting, pendingTaskIds } (:308) and store()[meeting.id] = closed (:345, the last statement of decide) are all memory writes. The only save() on the path belongs to task-create inside produceTasks (core/src/host/tasks.js:287), and that snapshot is taken before :345, so the archive on disk never learns that the meeting was decided.

The pendingTaskIds record added by #1765 is a memory write as well, so its retry protection holds across calls but not across a restart: after a restart the record is gone and the retry creates the cards again.

Expected

Every meeting write is durable before the call returns: open, position, block, resume, and both halves of decide (the pendingTaskIds record after the cards are created and before the decision file is written; the closed record after the file is archived). After a restart the meeting reads back [decided], a second decide is refused with meeting/closed, and no second card or second decision file is produced.

Scope

  • core/src/host/meetings.js — await rt.save() at those points. rt.save() is the existing convention in this host (schedules.js, tasks.js, project-assistant.js); no new API.
  • core/test/meetings-persist.test.js — the red-first test.
  • patches/ds-bot/0002-flybot-api-host.patch and patches/ds-bot/files/src/host/meetings.js — regenerated, not hand-edited.
  • changelog.d/…fixed.md.

Not in scope: an idempotency key for decide (a separate enhancement), any UI work, and the project-read wording note.

Acceptance

  1. Red-first on the base: decide → ctx.dispose() → a new runtime reads the meeting back as decided, and a second decide is refused with meeting/closed.
  2. The same test proves the failure shape: after the restart, the card count and the number of files in decisions/ are both unchanged by the refused retry.
  3. open + position survive a restart.
  4. No regression: the existing meetings suites plus fix(host): a meeting archives its decision before it creates cards #1765's red-first stay green.

Duplicate check

Searched open and closed issues before opening: meeting persist → #1736 (closed, meeting records), #1763 (closed, orphan card on archive failure — a different face); meeting restart state → #1550 (proposal), #1720/#1732/#1730 (closed, task contract); meetings save disappear → 0 results. Open issues that mention meetings are #1767 (board display, already has a PR) and #1550 (proposal). Nothing covers persistence.

Activity

  1. added a commit that references this issue on Oct 30, 2026
    8489264
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions