Skip to content
All releases

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 doctor grew a daemon-zombies row 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 doctor names a forked session. A new claude-session-fork row 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 w was the only way back. It now has its own ^b1 ACE tab, lit while you are on it, clickable, and a stop on the Alt-←/→ 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 main deploy. 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 refusal and 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 at 0% with "press U on a ticket to add one". A closed childless epic now renders 100%, (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/autoclose is now under the degradehonesty wall 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 send had no recipients on a Supabase source. The adapter called a flowforge_marketing_recipients function that no migration created, production answered 404 PGRST202, and marketing get still said ready. Migration 0077_marketing_recipients_rpc.sql creates 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 reads partial while 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 preflight names the missing function in its recipients.count check (#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 COCKPIT banner and the daemon-zombies doctor row point at any that still don't (#6351).
  • A Supabase recipient source needs the recipient function in that project, and no supabase.query key. Apply migration 0077_marketing_recipients_rpc.sql (it needs 0070 first), remove marketing.recipients.supabase.query if you set one, and flowforge marketing test recipients should 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 /clear is not grouped by the doctor row. The inspection window itself still refuses to fork in that case; only the claude-session-fork row 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 update on an existing install, or curl -fsSL https://get.flowforgesoft.com/install.sh | sh for a fresh one. Both write the same release binary to /usr/local/bin and ~/.flowforge/bin together. Then quit and relaunch every open cockpit.

v1.2.24 — 20 PRs, 24k lines, made with FlowForge