Release
v1.2.24
What shipped, in numbers
- 20
- PRs merged
- 24,312
- Lines added
- 3,586
- Code lines
- Code
- 3,586
- Tests
- 4,708
- Docs
- 16,018
- Sum · code + tests + docs
- 24,312
of 20 merged
- bug 14 70%
- feature 0 0%
- polish 0 0%
- docs 3 15%
- groundwork 0 0%
- unclassified 3 15%
since v1.2.23 · tagged
The letter
What shipped
The stability release. One cockpit killed and respawned the shared daemon every six to fourteen seconds, printed each kill over its own screen, and never reaped what it killed — until 5,242 dead processes filled the per-user process table. Opening ACE's inspection window started a second live ACE on the same conversation, and the two acted as controller side by side for hours. A binary promoted minutes earlier read "commits behind — cut a release". The site deploy cried "CHECKOUT IS DEAD" on every push to main while checkout was healthy. A GitHub hiccup at merge time read as a refusal only a human could clear. Six finished tickets drew as empty epics at 0%. Every one of those was hit in production in the last two days, and each is closed here. There is no new feature in this release, by design. What needs one step from you, and what is not fixed yet, are each stated plainly below.
The daemon stops killing itself
- 🔴 Every open cockpit killed and respawned the shared daemon every 6–14 seconds. The identity guard compared the daemon's build with the build of the cockpit that was running, but a respawn always starts the binary on disk — so a cockpit opened before an upgrade could never be satisfied, and two cockpits on two builds took turns killing each other's daemon. The guard now compares against the installed build, read from the binary itself; a cockpit older than the install is told so instead of killing anything:
⚠ STALE COCKPIT — this cockpit runs vX, the installed FlowForge is vY — quit and relaunch(#6351). - One slow reply no longer kills a daemon. A single health reply slower than one second counted as wedged, so daemons that were still starting were killed mid-boot. A daemon is now killed as unresponsive only after three misses in a row spanning at least 90 seconds, never while it is younger than 45 seconds, with a 5-second handshake. Only the live guard keeps that count between ticks: a launch, a
D-key retry or a CLI call never kills on a miss (#6351). - 🔴 5,242 zombie daemons. Killed daemons were never reaped, and a liveness probe answers "alive" for a zombie — which is where "still alive after SIGKILL" came from. Every spawned daemon is now reaped, a zombie counts as exited, and
flowforge doctorgrew adaemon-zombiesrow that goes red when a cockpit has<defunct>children, naming each cockpit and its count (#6351). - Error text no longer paints over the cockpit. The guard wrote to the terminal while the cockpit owned the screen. While the cockpit runs, stray output now goes to
~/.flowforge/logs/tui-<repo>.stderr.log, and the guard's own lines go to~/.flowforge/logs/daemon-guard.log(#6351).
One ACE, never two
- 🔴 The inspection window forked a second live ACE. It ran
claude --resume <ACE's session>, which does not attach to the running ACE — it starts another Claude with the same tools on a copy of the conversation. On 2026-09-15 that left two controllers acting on one lane for hours. When ACE is running, the window now opens a read-only view that follows ACE's transcript live, says on its border and its first line that nothing typed there reaches ACE, and closes cleanly on Ctrl-C. Resuming is refused while ACE runs, and when the process table cannot be read the window assumes ACE is running. With ACE not running, the window resumes the session as before (#6349). flowforge doctornames a forked session. A newclaude-session-forkrow goes red when any Claude session has two or more live processes, naming each pid and how it holds the session (#6349).- You can see where the inspection window is, and get back to it. It landed on window 1, but the top bar did not change, no tab lit, and nothing could be clicked or cycled to — so once you left it,
Ctrl-b wwas the only way back. It now has its own^b1 ACEtab, lit while you are on it, clickable, and a stop on theAlt-←/→cycle, all present only while the window exists (#6344). - The inspection window's tmux targets match exactly. A bare target falls back to a name prefix, so a window merely named like the inspection window could be probed, pre-killed or reaped at boot in its place. Every target is exact now (#6344, #6347).
Signals that stop crying wolf
- "N commits behind" on a current binary. A binary promoted minutes earlier read
3 commits behind — cut a release: the three commits were merges that changed no file. The banner now treats an identical tree as current, and still counts commits when the tree really differs (#6338). - "CHECKOUT IS DEAD" on every
maindeploy. The site's checkout gate reads a CloudWatch invocation count, and the deploy role was never granted that read, so every call was denied — and a denied read was counted as zero invocations. A denied read, or an empty one while the handler answered every probe, is now reported as cannot verify, and the verdict falls back to the body-dependent answers the handler gave. A real outage — the handler not receiving the request body — still fails the gate (#6331). - A GitHub blip at merge is retried, not shown as a refusal. A transient GitHub error during the merge step — a GraphQL "Something went wrong", a 5xx, a dropped connection, a secondary rate limit — read as
Unrecognised refusaland asked a human to relay an error that needed no human. It is now retried up to four times with 2 s, 4 s and 8 s backoff, and the PR head is re-read before every retry: a moved head stops the retry. A real refusal is never retried. When the retries run out, the cockpit names the GitHub-side outage and the attempt count (#6353). - A closed ticket with no children reads DONE, not an empty epic. Six closed single tickets carrying a
[META]marker drew in the War Room as epics at0%with "press U on a ticket to add one". A closed childless epic now renders100%,(closed, done), and never offers to add a ticket; an open childless epic is unchanged, and a[META]ticket with a real child stays an epic (#6354). - 🔴 A merge could close a ticket another open PR still owned. When the ownership guard's GitHub read failed — a rate limit, a 5xx, a dropped connection — it treated the failure as "nobody owns this ticket" and let the merge close it anyway, with nothing in the log, the Result, or the ticket saying the guard never ran. It now fails closed: a failed read blocks every auto-close that run would have made, names the cause on the Result, in the log, and as a comment on each ticket it would have closed, and
internal/autocloseis now under thedegradehonestywall so a future silent degrade there is caught by construction, not by memory (customer-reported, #6360).
Release Letters: the recipient read is one fixed function
flowforge marketing sendhad no recipients on a Supabase source. The adapter called aflowforge_marketing_recipientsfunction that no migration created, production answered404 PGRST202, andmarketing getstill saidready. Migration0077_marketing_recipients_rpc.sqlcreates it as a fixed function with no parameters, callable by the service role alone, returning confirmed accounts with their consent state and never an address that declined or unsubscribed (#6341).- A configured query is refused, never run. Running a caller's SQL inside a read-only transaction is not a sandbox — it still reads secrets and can stop other sessions — so the adapter posts no query at all, refuses a configured
marketing.recipients.supabase.query, and readiness readspartialwhile that key is set. A missing function is named with the migration that creates it, and a result the server truncated is refused instead of sent short.flowforge marketing preflightnames the missing function in itsrecipients.countcheck (#6341).
Colado
Colado is a jiu-jitsu term — a position is colado when it's justo, perfectly adjusted, no gaps, full pressure — and it's the name for the family of FlowForge gates that leave no space between the rule and the work. This release closes these gaps:
- Opening ACE's inspection window can no longer start a second ACE — resume is refused while ACE runs, and an unreadable process table counts as running (#6349).
- A merge retry can no longer land a commit nobody re-checked — the head is re-read before every retry, and a moved head stops it (#6353).
- The recipient read can no longer run a query someone configured — the set is fixed in one reviewed function, and a configured query is refused (#6341).
- A guard can no longer kill a daemon over one slow reply — three misses over 90 seconds, never inside the boot grace (#6351).
Small things that change everything
Not every change earns a headline. These fit in a line each, and you feel them every day.
- The ACE tab lights while you are on the inspection window, and a click takes you back (#6344).
- A just-promoted binary stops telling you to cut a release (#6338).
- Finished tickets stop drawing as empty epics in the War Room (#6354).
Smaller, and under the hood
- A flaky test that refused v1.2.23's third cut is fixed. A test double shared across the company-board fan-out was written concurrently without a lock; it is guarded now. No production code changed (#6329).
One setup step
- Quit and relaunch every open cockpit after you update. A cockpit started on an older binary keeps the old guard until it restarts, so the kill loop ends fully only once every open cockpit runs this build. The
⚠ STALE COCKPITbanner and thedaemon-zombiesdoctor row point at any that still don't (#6351). - A Supabase recipient source needs the recipient function in that project, and no
supabase.querykey. Apply migration0077_marketing_recipients_rpc.sql(it needs0070first), removemarketing.recipients.supabase.queryif you set one, andflowforge marketing test recipientsshould then pass with a count (#6341).
Not yet
- The deploy role still cannot read the checkout metric. Until that permission is applied to the role, the gate's CloudWatch half reports cannot verify on every deploy and the verdict rests on the handler's answers; a real outage still fails (#6331).
- A fork created after ACE runs
/clearis not grouped by the doctor row. The inspection window itself still refuses to fork in that case; only theclaude-session-forkrow can miss it (#6349).
Agents
No roster change in this release: FlowForge installs the same 44 specialists v1.2.23 shipped (git diff --name-status -M v1.2.23 origin/release/1.2.24 -- v3/packages/core/agents is empty).
Update
flowforge updateon an existing install, orcurl -fsSL https://get.flowforgesoft.com/install.sh | shfor a fresh one. Both write the same release binary to/usr/local/binand~/.flowforge/bintogether. Then quit and relaunch every open cockpit.