Gas City v1.5.1
v1.5.1 is the new stable release and supersedes v1.5.0. It is a patch
release from hotfix/1.5.1 at 4f698fe2a7, which differs from v1.5.1-rc1
only by the CHANGELOG release commit. It carries bug, security and
documentation fixes only: the fixes reported from a large multi-city
deployment on a shared rig store, a security fix for Claude permission
prompts, the split-storage migration repair, the bundled-pack pin move, and
Beads (bd) v1.3.2.
Install it with brew install gascity / brew upgrade gascity or from the
release assets (see Install). Read the Upgrading Notes first: a few
behaviours change, and two upgrades need a specific order (bd, then the
supervisor, then gc doctor --fix).
Highlights
- Multi-city deployments sharing a rig store stop disturbing each other.
The controller's pool orphan sweep and the coreorphan-sweeporder no
longer release claims this city cannot vouch for (#7763, #7895), with the
newgc agent is-foreignverb as the shared test. Unchanged reaper and
Dolt backup escalations mail once a day instead of on every run (#6363),
gc dolt compactno longer looks for a pinned rig's database on the
city's own Dolt server (#7997), byte-identicalbead.updatedrepeats are
no longer recorded on every reconcile pass (#7023), an exec provider
startthat exits 2 now fails the start, cost estimates price the
Claude 5 family and Opus 5.5 (#6384, #7713), and the run, session, agent
and order-history APIs report formula-v1 runs, closed sessions, pool
workers and timeouts correctly (#4703, #5934). - Security: text sent to a Claude session can no longer approve its
permission prompt (#6832). On Claude Code 2.1.28x gc missed the
permission dialog, so a submit or nudge could press Enter on a pending
tool call. gc now detects the prompt, refuses to type into it, and
respondpicks options by label. Thanks to Chris Sells (@csells) for the
fix. - Split storage is repaired and safer to migrate.
gc storage migrate
now clears the work store's frozen copies of moved beads after backing
them up (#5987), a city migrated by 1.5.0 refuses to boot until it is
re-migrated, and split cities get correct reads, slings, deletes, pool
demand, fenced writes and seat close gates (#6015, #5974, #6129, #6019,
#6917, #7480). - Bundled packs are pinned to what the binary embeds (#7070).
gc init
pins[imports.core]and[imports.bd]to a commit that matches the
embedded packs, the June 2026 pin keeps loading offline, andgc doctor --fixre-pins it. Dolt 2.2.0 is the pinned Dolt. - Beads v1.3.2. gc pins and is tested against bd v1.3.2, and the first
start after a bd upgrade repairs beads wrongly marked blocked by beads
migration 0059 (bd recompute-blocked, #7087). gc import add --version <semver>works for registry packs (#7071).
Versions resolve against the registry's[[pack.release]]entries, so the
"Exactly this version" command fromgc pack registry showworks, and
fetched content is verified against the release hash.- Supervisor fixes.
gc initandgc startrefuse to hand a city to a
supervisor from a different gc installation (#7046),gc supervisor start
on macOS starts the installed launchd job (#6966, #7048), a slow Dolt
identity probe is retried instead of failing startup (#7044),gc stop
holds the controller lock through store shutdown (#6742), and event-log
rotation no longer loses events (#7022). - Go 1.26.10. The toolchain moves to Go 1.26.10 (GO-2026-6809, eMudhra
CA distrust) after Go 1.26.9 andgolang.org/x/netv0.60.0, along with
OpenTelemetry, thrift and container-image security updates.
Upgrading Notes
Upgrade in this order:
- Upgrade bd to v1.3.2 (
bd versionshould report 1.3.2). The first
start of each city afterwards runsbd recompute-blockedonce per scope. - Restart the supervisor on the new
gc:gc supervisor stop --wait,
thengc start. This stops every city the supervisor hosts until
gc startruns. A 1.5.0 supervisor does not know the new bundled-pack
pin. - Run
gc doctor --fixonce per city. It clears the
packv2-import-statewarning by re-pinning[imports.core]and
[imports.bd]offline. - Split-storage cities migrated by 1.5.0 must be re-migrated:
gc stop,
gc storage migrate --from-work --fleet-stopped, thengc start. Until
then boot refuses the city (storage.binding.unconverged, outcome
retained-copies).
Then check the behaviour changes below: gc stop can now exit 1, gc doctor --json reports "ok": false on a blocking failure, a SQLite store with
wrapped ids needs gc storage repair-sequence, and exec adapters get SIGTERM.
- Exec adapters and setup commands are now cancelled with SIGTERM, not
SIGINT. When gc cancels an exec session adapter, or asession_setup,
session_setup_script,session_liveorpre_startcommand run by the
tmux or herdr provider, it sends SIGTERM to the command's process group,
then SIGKILL after the grace period: 2s, or 10s for setup commands when
[session] setup_max_timeoutis set (#7018, backported in #7953). Under
SIGINT a rollback trap could be skipped, leaving behind whatever the script
created: a shell that inherits SIGINT ignored (as a background job of a
non-interactive shell does) cannot trap it, and a SIGINT that lands during
a command substitution can wedge bash 5.2. If your script traps onlyINT,
addTERM(e.g.trap rollback INT TERM). Non-shell adapters that relied
on SIGINT's default action (e.g. PythonKeyboardInterrupt/finally) must
handle SIGTERM explicitly. The bundledscripts/gc-session-dockeralready
trapsTERM. - Upgrade Beads (
bd) to v1.3.2. gc 1.5.1 pins and is tested against bd
v1.3.2 (deps.envBD_VERSIONand the go.mod library), a stable release
that keeps bd v1.3.1's schema: v1.3.1 plus thebd purgelive-dependent fix
(beads#7031), the backup watermark fix (beads#7032), the blocked-state
recheck publication fix (beads#6876, backported in beads#7146), the skipped
no-opDOLT_CHECKOUTon fresh connections, so a least-privilege Dolt user
works with the native store (beads#7322), batched bulk ID reads, so
bd exportand gc'sjsonl-exportorder no longer stall on very large
wisp sets (beads#7603),bd purgekeeping--limitin its confirmation
hint (beads#7540), and a Go 1.26.10 / x/net / grpc / thrift security
rebuild. Install it withbrew upgrade beads(orbrew install beads),
from the
v1.3.2 release assets,
or withgo install github.com/steveyegge/beads/cmd/bd@v1.3.2. Read beads'
v1.3.2 upgrade notes
before upgrading scripts that call bd directly. A city that stays on bd
v1.3.1 keeps working: a proxied city or rig (the default for new cities)
always uses the bd CLI, andgc doctordoes not flag bd v1.3.1 there; one
that connects to its Dolt server directly falls back from its native store
to the bd CLI (version_compatpreflight gate), with agc doctor
beads-storewarning. - A city's first start after a bd upgrade runs
bd recompute-blockedonce
per scope. beads migration 0059 wrongly marks some beads blocked across
relates-to, discovered-from and other non-blocking edges (beads#7037). It
hits any store that crossed 0059: one upgraded from bd v1.2.x or older, or
one that ever ran bd v1.3.0 or v1.3.1. Those beads drop out ofbd ready
and gc stops dispatching them. gc repairs the city and each Dolt-backed bd
scope it owns, records the bd version in that scope's bd config
(custom.gascity.blocked_repair_bd_version) so later starts skip it, logs
recomputed is_blocked for <scope> under bd <version>: N rows corrected,
and emitsbeads.blocked.recomputed(a fresh city records one 0-row event
per scope). The supervisor does the same, andgc rig addrepairs the rig
it adds. It does not touch external Dolt servers, complete storage bindings
or non-bd providers: runbd recompute-blockedin those scopes yourself.
Scopes are repaired four at a time before agents start: a city with three
rigs and 5,000 beads per scope took about 1.5s. A failed repair only warns,
and gc retries it on the next start. The repair runs only when a city
starts: if you upgrade bd while a city is running, rungc restartfor it
(orgc stop, thengc start), or restart the supervisor and every city it
hosts withgc supervisor stop --wait, thengc start.gc starton a
city that is already running does not repair it (#7087). - A city migrated by
gc storage migratein gc 1.5.0 refuses to start
until you repair it. 1.5.0's migration kept the work store's copy of every
bead it moved, and any reader that reached the work store first was served
the frozen copy, so finished workflow steps were dispatched again and again
(#5987).gc storage migratenow clears those copies after the copy is
proven: it writes every row the copy manifest names, with its edges and
payloads, to<binding root>/infra.retained-source.jsonl, re-reads the
backup and checks it against the source, and only then removes the rows.
Rows the manifest never proved block the clear and name
gc storage recover-stranded. Boot refuses a converged city that still
holds copies (storage.binding.unconverged, outcomeretained-copies)
and prints the repair:gc stop, then
gc storage migrate --from-work --fleet-stopped, thengc start. The same
run resumes an interrupted clear.gc storage statusreports the backup
and the retained copies and exits non-zero while copies remain. After a
clear, pointing[storage.classes]back atwork(or deleting
[storage]) is refused, because the work store no longer holds that
state; rolling back now needs the backup. The backup holds the bead fields
the copy carried, not bd-only fields (notes, comments, close reason,
history). On bd and native Dolt work stores, a blocking edge from a work
bead into a still-open moved bead, andtracks/relatededges into moved
beads, cannot be kept: migrate lists each one before it deletes anything,
records it ingc storage statusand on thestorage.binding.*event
(lost_cross_edges), and keeps it in the backup. Of the six split-city
issues that 1.5.0's "gc storage migrateis experimental" note listed as
open, #5987 is fixed by this change, and #5974, #6015 and #6129 are fixed in
this release (see Fixed). #6242 was already fixed in 1.5.0:
gc convoy delete-sourceandreopen-sourceread the relocated graph
binding (#5633). #6348 applies only to a city that relocates nudges alone,
which boot refuses: this build serves either no relocated class or all five
infrastructure classes on one binding.gc storage migrateis still
experimental: rungc storage preflightfirst, back up every store
involved, and do not migrate a production city you cannot restore. See the
"Upgrading a city migrated by gc 1.5.0" section of
docs/runbooks/split-storage-classes.md(#7074). gc initandgc startrefuse to register a city with a supervisor
from a different gc installation. When the running supervisor's
executable is a different file from the invokinggc(for example a
Homebrew launchd supervisor and a tarballgc), registering would hand the
city to the other binary. Both commands now refuse and print both
identities and the fix:<this gc> supervisor install --forcefor a
launchd or systemd supervisor, or stop a directly started one.gc init
has already written the city and tells you to rungc startafterwards.
Pass--allow-supervisor-mismatchto keep a deliberate mixed setup;gc startthen also skips the build-drift restart, which could only relaunch
the other binary. An in-place upgrade of the same executable never refuses:
gc startrestarts the supervisor on build drift, now also after a
Homebrew upgrade, where both build ids readunknownand the release
version decides.gc statusonly warns, on stderr. The check adds one
/healthcall (2s bound) when a supervisor is running and is silent when
none answers (#7046).- Restart the supervisor on the new binary before running
gc doctor --fix. gc 1.5.1 moves the pingc initwrites for the
bundled[imports.core]and[imports.bd]packs to a commit whose trees
match the content this binary embeds. The June 2026 pin every gc through
1.5.0 wrote is now always served from the embedded packs and never fetched
(fetching it would install the June core pack), so an upgraded city keeps
loading unchanged.
gc doctornow reports the old pin as apackv2-import-statewarning, and
--fixre-pins it offline. A 1.5.0 supervisor that is still running does
not know the new pin and fails with "locked but not cached" until
gc import installfetches it, so rungc supervisor stop --wait, then
gc start, on the new binary first. Stopping the supervisor stops every
city it hosts untilgc startruns. Each superseded pin gets its own
synthetic cache directory untilgc doctor --fix. Downgrading a city
initialized or re-pinned by 1.5.1 to gc 1.5.0 needs onegc import install
with network (#7070). - Dolt 2.2.0 is the pinned Dolt.
deps.env, the installers, CI, the
container images and the Kubernetes manifests move from Dolt 2.1.7 to 2.2.0,
the Dolt the pinned beads release is tested on. Do not move a city to Dolt
2.3.x: 2.3.0 and 2.3.1 breakCALL DOLT_RESET('--hard')on some fresh
databases, and later releases have not been measured by beads (#7070). gc stopexits non-zero when it cannot confirm that every session
stopped. When listing sessions fails completely or in part, the orphan
sweep's listing fails, or checking a target session returns an error,
gc stopno longer printsCity stopped.and exits 0. It still stops
every session it did see and shuts down servers and the bead store as
before, names the sessions it could not verify, prints
gc stop: stop not confirmed: ...and exits 1; on the supervisor path the
registration is restored. An absent runtime server still counts as a
complete, empty inventory, so stopping a stopped city still succeeds, except
on the herdr runtime, which cannot tell a missing server from a failing one:
a repeatedgc stopof a herdr city whose server is already gone reports
stop not confirmedand exits 1. Scripts that treat a non-zerogc stopas
fatal should resolve the reported runtime error and rungc stopagain; on
herdr, once you have confirmed the herdr server is not running, treat that
exit as stopped (#6764).- An exec session provider's
startorprovisionthat exits 2 now fails
the start. Exit 2 meant "unknown operation, treat as success" onstart
andprovisiontoo, so aset -ewrapper whosegreporjqexited 2 duringstart
counted as started and the half-created box was never cleaned up. Neither op
can be unknown, so exit 2 from them is now a failure, and a failedstart
runsstopto tear the box down unless the name was already in use. Wrapper authors: return 0 fromstartand
provisionon success. Exit 2 keeps meaning "unknown operation" for other
undeclared operations. - A SQLite bead store holding wrapped ids refuses to mint until you run
gc storage repair-sequence. Older builds could wrap a SQLite store's id
sequence past 9223372036854775807 into<prefix>--<n>ids and then re-issue
ids. gc now keeps a persisted, fsynced floor per prefix and never re-issues
an id: on a store where a negative<prefix>--<n>id ranks above the floor,
creates fail with an error naminggc storage repair-sequence. Run it
without--floorto see the floor and the highest id; the command help
explains whether to delete stray rows or raise the floor (a negative floor
on a wrapped store cannot be undone). Stop every process running an older
build before repairing. Builds without this fix refuse to open a store whose
floor is negative (#6834). gc doctor --jsonreports"ok": falsewhen a blocking check fails.
The JSON envelope always said"ok": true. It now matches the exit code:
falseonly whenblocking_failedis non-zero,truefor advisory-only
failures. Automation that readokalone was missing real failures
(#5950, #6006).gc import add --version <semver>resolves registry packs against the
registry's releases. A pack published in a configured registry, such as
the gascity-packs catalog, is versioned by its[[pack.release]]entries,
not by git tags, so the "Exactly this version" command
gc pack registry showprints failed withno tags ... match constraint.
Worse, a range such as--version '>=0.1.1', a plaingc import add, and
gc import install/upgradeof a semver constraint resolved against the
gascity-packs repository's own tags and could lock the wrong content (for
bmad,0.4.0, a repository tag that is no pack release). Every lock sync
(add, install, upgrade,gc rig add, doctor repairs, init) now resolves
those sources against the registry's non-withdrawn releases, records the
release version and commit, and verifies the fetched content against the
release hash; a mismatch fails without writingpack.tomlor
packs.lock. A plain 1.5.0gc import addof a registry pack recorded a
range built from the repository tag it locked (for bmad,
version = "^0.4"), and that range matches none of the pack's releases:
gc import upgradefails with
registry pack main:bmad has no release matching "^0.4" (available: ...),
and once that run has cached the registry catalog,gc import installfails
the same way. Setversionin every such[imports.<name>]entry to a
range of the listed releases (for example^0.1), then run
gc import upgrade; one import left unedited fails the whole sync. For a
city with a single such import,gc import remove <name>followed by
gc import add <source>also works (#7071).- A formula step that means "blocked, stop the graph" must close with
gc.outcome=fail. A formula step bead (one carryinggc.step_ref) that
closes withgc.outcome=passnow satisfies its dependents even when it also
recordsgc.work_outcome=blocked, so workflows no longer stall after such
a step. Plain work beads are unchanged: a blocked work outcome still
withholds their dependents. bead.closedis emitted for every close gc observes. A
Update(status=closed)now emitsbead.closedinstead ofbead.updated,
re-closing an already-closed bead no longer re-emits, and orders and
autoclose now fire for closes made withbdorgc bd(#6860).nudge-on-routeno longer retries a failed nudge on a repeated, unchanged
bead.updated. The controller now drops abead.updatedthat repeats the
last one recorded for the bead within 20 minutes (see Fixed). The order
nudges a routed bead's target once, and it used to retry a failed nudge each
time the unchanged bead was announced again; it now retries when the bead
changes, or when the unchanged bead is recorded again after 20 minutes.
Other orders and scripts that trigger onbead.updatedlikewise no longer
see those repeats (#7023, backported in #8228).- API session creates for a demand-only singleton are refused.
POST /sessionswithkind: "agent"for a pool agent with
max_active_sessions = 1,min_active_sessions = 0and no
[[named_session]]now returns 400demand-only-singletoninstead of 202
and a session that never started. An API wake of such a session records the
wake (clearing holds) and returns the same error unless the session is
running or its start is already in flight (#6858). - A partially routed convoy sling answers 200 with status
partial.
POST /v0/city/{city}/slingreports a convoy whose children were routed
only in part as 200 withstatus: "partial"and the failed children in
batch.failures, so API clients must checkstatus, not only the HTTP
code. A convoy where nothing routed is still an error. A remotegc sling
older than 1.5.1 does not checkstatus: it exits 0, and its--json
output reportssuccess: true. Upgrade remotegcclients alongside the
server. - Supervisor API status codes and payload shapes changed for several
edits and reads. API clients that branch on the old codes or onnull
fields need updating:- Edits to a rig-qualified agent served by a generic
scope = "rig"
template (PATCH,suspend,resumeon/agent/{rig}/{name}) now
apply to the template, and so to that agent in every rig the template
serves, and return 200 instead of 404;DELETEon such an agent returns
409; delete the template instead (ga-l24lrx, #7076). - A config edit whose
[patches]entry names a target missing from the
merged config returns 400 instead of 500. - Closed sessions report
state: "closed"instead of an empty state, on
GET /session/{id}and in the session list, which returns them for
?state=closedor?state=all. - Unset optional fields are omitted instead of sent as
null: the map and
struct fields of agent, rig and provider patches (Env,
OptionDefaults,Pool,ContextAdvisory,FormulaVars), and the
absent records andMetadataof external-messaging inbound/outbound
results. The OpenAPI spec no longer lists them as required.
- Edits to a rig-qualified agent served by a generic
- The reaper's age gates use UTC on non-UTC Dolt servers. Bead
timestamps are stored in UTC, but the reaper compared them with the
server-localNOW(). It now usesUTC_TIMESTAMP(). On a UTC server
nothing changes. West of UTC, the first run after upgrading catches up on
the closes, stale-issue closes and session prunes the old gates delayed by
the server's offset; east of UTC, rows the old gates handled early now wait
the full configured age. The session-prune guard now counts candidates on
the same UTC cutoffbd pruneuses, so it can no longer miss non-session
beadsbd prunewould delete (#7136, backported in #7498). - An unchanged reaper or Dolt backup escalation mails once a day, not on
every run. Thereaperorder (every 30m) and themol-dog-backuporder
now raise their failures as incidents.escalate.shrecords each delivered
incident underGC_ESCALATE_INCIDENT_DIR(default<city>/.gc/incidents)
and, while its fingerprint is unchanged, sends nothing until
GC_ESCALATE_REMINDER_SECONDS(default 86400) have passed; a changed
fingerprint mails at once, and a clean run resolves the incident so the
next failure mails at once too. A suppressed run says so on stdout and
exits 0. To have the next run mail an incident at once, delete its
.gc/incidents/*.statefile. A pack that overridesescalate.shnow
receives--incident-key KEY --fingerprint Fon an alert and
--resolve-incident KEYon a clean run: the override must accept or
ignore those flags and must send nothing for--resolve-incident. An
override written for 1.5.0, which rejects the flags as a usage error (exit
2, or an error that names the flag), keeps alerting: the order sends the
alert again without them, so it mails on every run, without
deduplication, and the order's output names the override as predating
incident keys. See "Why a maintenance alert did not repeat" in
docs/getting-started/troubleshooting.md(#6363 and #8231, backported
in #8218 and #8232).
Added
gc storage repair-sequenceinspects a SQLite bead store's id-sequence
floor and, with--floor, raises it (--dirfor a store other than the
city's SQLite infrastructure binding,--prefix, defaultgcg). See
Upgrading Notes (#6834).gc storage migrate --from-backupre-copies a cleared city's binding
whose database is gone from the retained-source backup, with edges and
payloads. It runs only in that state;--from-workrefuses there, since the
work store no longer holds the slice (#7074).--allow-supervisor-mismatchongc initandgc startregisters or
starts a city even when the running supervisor is a different gc
installation (#7046).gc agent is-foreign <identity>reports whether this city can vouch for
a claim's assignee. It exits 0 (local) when the identity resolves
against this city's roster, or when one of this city's own session records,
open or closed, answers to it under its bead ID, session name, alias,
configured named identity or sole alias-history entry; 1 (foreign) when
neither does; and 2 (unknown) when it cannot answer (no roster, a failed
lookup, an unresolvable city, bad usage).--jsonadds the reason
(roster,session_record,foreign_binding,absent_from_roster,
no_config,lookup_failed) and its detail.localis not a liveness
claim. Scripts and formulas that reap claims should ask it instead of
re-deriving identity rules, and treat anything but exit 0 as "protect"
(#5955, #7895, backported in #8230).
Changed
- Ready work in a SQLite infra ledger is ordered priority-first. On a city
that relocates classes to asqlite-beadsbinding, that ledger's ready read
returned rows oldest-first (created_at, id). It now returns the canonical
(priority, created_at, id)order thatbd readyand gc's cached ready
reads already use, and appliesLimitafter sorting. When several ready
beads in the ledger route to one pool template, the bead a new session takes
its trigger, worktree and pack from is now the highest-priority one, oldest
first within a priority; the order in which sessions claim work does not
change.GET /v0/beads/readyalso returns the relocated graph leg's rows in
this order (#6863). gc supervisor starton macOS starts the installed launchd job. When
the supervisor LaunchAgent is installed and launches thisgc,gc supervisor startloads and kickstarts it instead of forking a detached
supervisor that ran outside launchd's KeepAlive and passed every provider
credential in your shell to agent sessions, even with
GC_SUPERVISOR_OMIT_PROVIDER_CREDS=1. Where launchctl cannot start the job
(no GUI session over SSH, CI) it warns and forks as before. A plist that
launches a different gc installation is never started;gcforks and names
<gc> supervisor install --force(#6966, #7048).- A slow Dolt identity probe is retried instead of failing
gc initand
gc start. Thedolt configidentity probes (user.nameand
user.email) ran once with a fixed 2s deadline, so a loaded host stopped
startup withdolt config probe timed out after 2s. They now get a 5s
budget, then retries at 10s and 20s with a stderr notice per retry, and
share a 60s total. Thedolt versionandbd versionprobes, whose timeout
only skipped the minimum-version check, get the same retries. A probe that
exits on its own still reports its result at once (#7044). - The
gascity/rolesrig pack is served from the gascity pack's embedded
copy.gc initwith the default gascity template andgc rig addno
longer clone the whole gascity-packs repository for it, so they work
offline. Existing cities need no migration; once the old clone has not been
modified for--keep-daysdays (default 7),gc import prune --apply
reclaims it (ga-73eoo, #7005). - Tutorial 01's sample
city.tomlmatches whatgc initwrites, and it
explains thatsha:<pinned commit>on thecoreandbdimports names the
copy of each pack bundled withgc(#7045). - Config, exec-provider, agent, orders and API reference docs now match the
code. Thewisp_ttlexample was"7d", which does not parse (Go
durations have no day unit) and leaves wisp GC off; it is now"168h".
idle_timeoutandmax_session_agesay they do not apply to
mode = "always"named sessions, the Gastown recipes stop setting
idle_timeouton the always-on mayor and show how to suspendboot, and an
exec order's defaulttimeoutreads 300s, not 60s. The exec session
provider page lists every fieldstartreceives, the per-operation
deadlines, thestopafter a failed start and what the sessionenv
carries, and no longer callsstartfire-and-forget, says gc sends the
startupnudgethrough thenudgeoperation, or says the start config
leaves outready_prompt_prefixandready_delay_ms. The agent guide covers
Claude's builtinpermission_mode = "unrestricted"andeffort = "max"
defaults and how to override them; the orders tutorial coversrig = "*"
and not pairing an override withskip; the API reference says what each
endpoint's agent count counts and that the supervisor serves its
spec at/openapi.json,/openapi.yamland/docs; the events reference
says thecache-reconcileactor names the cache, not whoever closed the
bead; the one-controller rule in the hardened-city runbook now covers every
city and Kubernetes rollouts; and thecontrib/scripts link to pages that
exist (#8212).
Fixed
Split storage (class bindings)
- Split storage:
gc bd showandgc bd dep list/dep treeno longer
answer a relocated bead from the work store's pre-migration copy.
gc bd show --long <id>,gc bd show <id1> <id2>and
gc bd dep list/dep treeof a relocated bead fell through to bd on the
work store and answered from the retained copy; unserved by-id reads now go
through the same residence check as writes (#6015, #7066). - Split storage:
gc sling <target> <formula> --formulaworks on a city
whose graph class is relocated. It failed withsetting gc.routed_to on gcg-1: ... bead not foundand leaked an open, unrouted wisp per attempt;
the root is now routed through the store that minted it (#6054, #7059). - Split storage:
gc slingrefuses a reserved coordination-class id
(gcg-,gcm-,gcs-,gco-,gcn-,gcnq-) that no store holds,
instead of creating a new task bead titled with it (#5974, #5975). - Split storage:
gc workflow deleteandgc convoy delete-sourcereach
ephemeral steps in a relocated binding. Wisp-tier steps, ephemeral roots
and nested workflows launched from a wisp step were left open and rootless
(#6129). - Split storage: pools demand-spawn for routed work in the city work
ledger. On a converged split city the pool-demand probe read only the
class binding, so a routed bead in the work ledger was claimable but never
counted; demand now counts over the work ledger then the binding, as
gc readyreads them. Single-store cities are unchanged (#6019). - Fenced writes to relocated classes honor
[beads] conditional_writeson
split cities. On a city that relocates sessions, graph, messaging, orders
or nudges to a storage binding, nothing stamped the conditional-writes mode
onto the binding engine, and the one-shot CLI's emitting wrapper hid the
stamp again. Underautoandrequireevery fenced write to a relocated
class (session wake/kill fences, pending-create rollback, molecule attach,
control epoch, drain reservations) silently took the unconditional legacy
path. The binding engine is now stamped, the SQLite engine reports whether it
can fence, therequireboot preflight probes the binding once per engine,
andgc statusreports the binding engine's mode (#6917, #7194). - A control row only wakes the control dispatcher of its own root scope on a
split city. The defaultscale_checkprobe re-read the durable route of a
control bead whose route repair had been suppressed or deferred, and on a
class-binding city every dispatcher reads the binding, so the dispatcher the
stale route named was demand-spawned for a row it must not serve (#7206). - Split-storage cities no longer close seats that still hold work-store
claims. On a city whose sessions live on a class binding, the pool-slot
close gate, the drain-ack escalation, the pool-slot retire and the stranded
repair read the sessions binding as the work leg and never saw claims on the
city work store, so seats closed while holding claims and the stranded
branch never ran. Every close gate now reads the census's leg set (the city
work store, the serving rigs, the bindings) for every seat, from a per-tick
seat work index that reads each leg once per tick. A rig-bound seat's
city-store claim now holds the seat, and rig storescity.tomldoes not
declare are no longer read (#7480). - The close cascade releases a closed seat's work on every leg. On a split
city a dead or closed seat's claims on the city work store and the rigs
stayed on the closed row until the orphan sweep reset them. The cascade now
releases on the gates' legs and under the gates' identities (a claim made
under an old alias is left to the orphan sweep), fencing each release on the
row's status and assignee (#7487). - A claim made after a tick's read stops the close. Before a gate-decided
close, and before an idle-timeout kill, gc re-reads the seat's claims and
keeps the seat when it finds one; a close that releases anyway releases the
late claim too. The re-read is live on local stores and answers from the
cache only on the work store and bd rigs: when the cache cannot answer, the
close or kill goes ahead on the tick's read, and a late claim there stays on
the seat (#7525). gc bd show <id>works again on a refused split city whose binding holds
migrated beads. The by-id relic refusal now applies per id: only an id the
class binding holds, or that the migration's copy manifest records
delivering, is refused as a frozen pre-migration copy. Every other work bead
reaches bd. Without a readable manifest the old city-wide refusal still
applies whenever the binding holds any id outside its reserved prefixes
(#7430).gc storage statusreports a boot-plan refusal and exits non-zero. Two
of its three paths, including a served split city, returned 0 without
resolving the boot plan; the refusal is now reported in the readout and in
the exit code. An unsupported[storage.classes]arrangement is still
reported only bygc start: status printsbinding: nonefor it and exits
0 (#5219, #5233).- Large graph cooks into a SQLite store no longer fail with
database is locked (517). The id allocator no longer overflows into a
walk over occupied ids, the graph transaction startsBEGIN IMMEDIATE
(also when the store path does not parse as a URL), and busy retries back
off with jittered exponential backoff. MemStore's pinned-id sequence lift
uses the same strict suffix parse as SQLite, so an overflowed pinned id
cannot wrap the sequence (#6827, #6833).
Claims and orphan sweeps
- Releasing a dead session's claim no longer erases a newer claim on the
same bead. With[beads].conditional_writesatautoorrequireon a
capable store, the retired-session and orphaned-pool release paths clear the
assignment and affinity metadata in one write guarded by the snapshot's
revision, so a successor that re-claimed the bead after the controller's
List(even under a reused assignee name) is left alone. Native-store list
rows carry that revision. The guard cannot tell a newer claim from any other
write, so a status, assignee or metadata change since theListis reported
as a conflict, the bead is left as it is, and the next tick decides again.
requirerefuses a store without the capability,autodegrades to the
legacy release, andoff(the builtin default) is unchanged. A snapshot
without a revision (a row not rewritten since its store began recording
revisions) keeps the legacy status-and-assignee fence (#7384, backported in
#7581). - The core
orphan-sweeporder no longer releases claims it cannot vouch
for. On a rig store shared with another city it used to release that
city's live claims as dead, in every shape they take:<rig>/<name>crew,
foreign-binding pool instances (<rig>/<binding>.<name>-<n>), runtime
session names and bare session bead IDs, with a false cause note and a mail
for each sweep (#5955). The order now asksgc agent is-foreignabout every
claimant that is neither configured nor live, in HQ and in every rig, before
any revalidation or session probe, and keeps the claim unless the verb
answerslocal. Each sweep that keeps claims printsprotected K foreign/unknown claimsin its summary and a line naming every kept identity
and its claims, and declares apartialoutcome, which the controller
records as anorder.skippedevent with one scope per identity; the run
still exits 0, and a sweep that only protects sends no mail. The verb is
asked at most once per distinct identity per sweep, within a gate budget of
GC_ORPHAN_SWEEP_GATE_BUDGET_SECONDS(default 120, under the order's 4m
timeout); past it, the remaining claimants are not asked, their claims are
kept and named with a "gate budget … exhausted" reason, and the sweep says
how many identities it did not ask. Wheretimeoutis installed, an ask
still running when the budget runs out is abandoned and its claim kept and
named too, so one stalled store cannot run the sweep into the order's
timeout. Identities are asked from a random starting point each sweep, so a
large set of foreign claimants delays, but does not starve, release of a
dead local claim. Unlike the Go
pool sweep, the order has no route filter, so it gives bare identities no
shape exemption: a dead local ephemeral or bead-ID claim stays releasable
through its own session record. Migration notes: a person's claim, or a
local agent's claim whose config and session records are both gone, is now
kept and named every sweep instead of released; release it by hand with
gc bd release-if-current <id> <assignee>(#7895, backported in #8230). - The pool orphan sweep treats a claim it cannot observe as unobservable,
not dead. On a shared store, an assignee in<rig>/<name>form that this
city's config does not mint — another city's identity, a binding this city
imports only on another rig, or an agent, binding or namepool name since
REMOVED from local config — used to be judged by local session evidence
alone and silently stripped (in_progress→open, assignee cleared).
The dead-assignee sweep now keeps such a claim unless one of this city's
own session beads, open or closed, answers to the identity under its alias,
its configured named identity or its sole alias-history entry (looked up per
identity, at most once per pass), and logs one
protected N foreign/unknown identitiessummary per pass naming each
identity it kept and its claims. A namepool seat whose name was trimmed
therefore stays reapable after it closes; claims under the earlier aliases
of a seat renamed more than once are kept and named. The orphan-close
release of a confirmed orphaned session is unchanged. The coreorphan-sweep
order now applies the same gate throughgc agent is-foreign; see the entry
above. Migration notes: a<rig>/<name>identity moved to another city on
the shared rig store keeps being released here until this city's closed
session records for it are purged or re-aliased.
The session record of a removed agent, binding or namepool name lasts until
closed session beads are purged, which happens only after
GC_INFRA_SESSION_PURGE_AGE(default 72h) on a relocated SQLite sessions
ledger withwisp_ttlset, or afterGC_REAPER_SESSION_PURGE_AGE(default
720h) in the city store when the reaper'sGC_REAPER_SESSION_BEAD_PATTERN
(defaultgm-*) matches the city's session bead IDs or is set to"".
Anywhere else closed session beads are kept indefinitely. Once the record is
purged, the sweep protects the identity's outstanding claims, so release them
explicitly (gc bd release-if-current <id> <assignee>/reassign) when
retiring one, and watch the summary line for claims held back (#5959,
#7763, backported in #8210). - Namepool and other stable-alias seats are no longer retired while they
hold a claim. The awake, drain and reuse guards now match claims recorded
under a session's stable alias (#6413, #6766). - The core
orphan-sweeporder no longer misses live sessions when the
session list exceeds the pipe buffer (#6714, #6728). - The core
orphan-sweeporder times out a minute before its next run is
due. The order declared no timeout, so it ran under the 300s exec
default, equal to its 5m interval. The cooldown counts from when a run
starts, so a sweep killed at its timeout was due again at once, and on a
slow store the sweep ran continuously. The order now setstimeout = "4m",
which leaves at most two minutes after the 120s foreign-identity gate
budget, less the claim collection that precedes it. A city whose sweep
needs longer can raise it with[[orders.overrides]],
name = "orphan-sweep"and atimeout(plusrig = "*"if a rig's own
pack also carries the order).
Sessions, pools and runtimes
- Closed session rows no longer read as live. On file-backed and SQLite
stores, closing a session wrote its terminal metadata and its closed status
as two writes, so a concurrent wake could leave a rowstatus=closed state=awakethat the reconciler andgc session listtreated as running.
Session closes, controller closes and pending-create rollbacks now use one
revision-fenced write; a stale status heal no longer reverts a concurrent
gc session suspend,killor close; a terminal provider error during a
pending create closes the row asfailed-create; and ACP startup and
rollback are fenced so a completed start is not closed as failed (#6109,
#6114, #6583, #6745, #6747, #6756, #6784, #6802). - The tmux runtime stops, reads and types into sessions by exact name.
tmux resolves a bare-t nameby unique prefix, so stopping an
already-exitedworker-1killed a liveworker-10, and metadata reads,
send-keysandcapture-panecould land on another agent. The tmux
runtime's stop and liveness checks, metadata reads and writes,send-keys,
paste-buffer,capture-pane, rename and respawn now use an exact target,
and a tmux metadata read error is no longer reported as an unset key. Still
matched by prefix in this release: every session-targeted command of the ssh
runtime, whose stop and relaunch can still kill or replace a prefix-matching
remote session (fixed on main by #7682), and the tmux runtime's
set-option,set-hook,resize-pane,attach-session,switch-client,
select-window,list-windowsanddisplay-message -d(#6876). - tmux session state no longer reads every session as absent on a busy
city. Steady invalidation could keep the state cache from publishing any
fetch until it went stale, so healthy sessions were drained, restarted or
reaped (#6800). - One agent on a harness that cannot receive MCP no longer fails the whole
city's start. gc skips that agent's MCP projection with a warning naming
the harness and the fix; that agent's own session start still fails with the
same error (#6661). - The codex, copilot and antigravity managed hooks use the launcher's
gc(${GC_BIN:-gc}, with user bin directories appended toPATH), so
an oldergcin~/go/binno longer rewrites a fresh city's bd shim
(#6579). - Drained on-demand named sessions wake for routed demand aimed at their
identity (#6692). - The execution backstop's stall latch is bound to the session incarnation
and expires after 30 minutes, so a supervisor restart mid-drain no longer
silences it for the rest of the seat's life. The session bead records the
current hold inexecution_claim_holdas<reason> <time>(#5701). - herdr agent starts retry when a fresh pane reports
agent_pane_busyon a
non-zero exit (#6637). - A nudge that races an ACP agent's exit is dropped with a stderr line instead
of failing withfile already closed(#6540). - Claude Code's feedback-survey dismissal no longer submits a stray
0into
the session (#6859, #7013). - A gateway login reaches the Claude provider readiness probe.
- A session can close a bead it claimed under its session bead id without
--force;gc bd closeruns bd as that id. Claims recorded under a session
name or alias still need--force(#6324). The close's audit actor is the
assignee string. gc session pinandgc session wakereport a demand-only singleton's pool
session honestly instead of succeeding: pin is refused, and wake records the
wake but exits non-zero unless the session is running or its start is
already in flight (#6858).
Beads, Dolt, events and maintenance
gc stopholds the controller lock through bead-store shutdown, and
gc start --foregroundtakes it before starting the bead-store
provider, so a racing start or supervisor restart can no longer run a
controller against a provider thatgc stopis retiring, and a racing
gc start --foregroundno longer starts or restarts that provider. A
supervisor-hostedgc startorgc registerthat races agc stopstill
runs the bead-store start before it takes the lock, then waits for the stop
and starts the city again (#6742, #6789).gc stopstops the city's nudge pollers and never reopens a stopped city's
store (#6857).- Event log rotation no longer loses events. A short-lived
gccommand
could rotateevents.jsonlfrom a stale file handle, after which the
supervisor appended to a deleted file; one city lost about 225,000 events.
Only the supervisor's recorder (or a standalone controller holding the
controller lock) rotates now, every writer locks a stable
events.jsonl.locksidecar and reopens a replaced handle, sequence numbers
stay monotonic across rotation, and watchers no longer hold a rotated file
open. The on-disk format and archive names are unchanged (#7022). - A run the controller deletes no longer stays
pendingin the run
census. When the controller's wisp GC deleted a run whose rows its bead
cache did not hold (closed rows, a restarted controller, an evicted entry),
or the read just before a delete failed, nobead.deletedevent was
recorded. The run view drops a run only on that event and replays the
retained event log on every restart, so the run stayed listed aspending,
and canceling it returnedrun-not-found. Every delete made through the
controller's bead cache now recordsbead.deleted, carrying just the bead
id when no copy of the row is at hand, and a bead listed twice in one batch
records it once. Deletes made outside the controller, such as a script's
bd purgeorgc bd delete, are not covered, and runs stranded before this
fix stay listed (#5934). - The controller no longer records an unchanged bead as a new
bead.updatedevent on every reconcile pass. Its bead caches announced
rows whose state had not changed, so on a busy city mostbead.updated
events were byte-identical repeats, and each one fed every event consumer,
from the controller's own cache and wake path to orders that trigger on
bead.updated. A cache now drops abead.updatedwhose payload is
identical to the last create or update the event log acknowledged for that
bead within 20 minutes; after that an unchanged bead is recorded again.
Creates, closes and deletes are never dropped, and a close or delete resets
the bead. A failed append is logged and not remembered, so the next copy is
recorded. See Upgrading Notes fornudge-on-route(#7023, backported in
#8228). - Concurrent
gc initruns sharing a Dolt database no longer force a
second initialization over an active migration, and a verified-empty
store is initialized with plainbd initinstead of a forced reinit that
could stop partway through the schema on a loaded host (#6658, #6709). gc dolt compactno longer looks for a pinned rig's database on the
city's managed Dolt server. A rig pinned to another Dolt endpoint
(gc rig set-endpoint --external, a rig-local--selfserver,
[[rigs]] dolt_host/dolt_port, or a bd binding to a remote host) keeps its
database there, but the compactor queried the managed server for it. The
mol-dog-compactororder failed every two hours withError 1049 (HY000): database not found, or, when the managed server still held a same-named
copy, flattened that copy and reported the rig's database as compacted.
Compact now skips such a rig with a log line naming its endpoint, and skips
a same-named copy on the managed server with a warning;--only-dbcannot
reach either. A rig whose.beads/config.yamlcannot be read is skipped
too, with a warning naming the file. Rigs that inherit the city endpoint are
compacted as before (#7994, #7997, backported in #8215).- The Dolt compactor docs no longer claim it only rewrites history this
city grew. Thegc-compact-basewatermark is a position, not an author:
a database several cities write to is flattened for all of them when the
city whose managed server holds it, or any city that reaches it over
loopback, compacts it. A city skips an external endpoint on a non-loopback
(for example cloud-hosted) Dolt host with exit 0, so it never compacts that
database. "Shared and non-local databases" in
docs/troubleshooting/dolt-bloat-recovery.mdcovers keeping a shared
database out ofmol-dog-compactorand a stop-everythinggc bd compact
procedure (#7896, #7993, backported in #8215). - The reaper's session prune no longer escalates a stale or missing backup
when it has nothing to delete. The pattern path
(GC_REAPER_SESSION_BEAD_PATTERN, defaultgm-*) checked the city store's
backup age before anything else, so a city without a fresh bd backup of its
city store raised a MEDIUM "Reaper anomalies detected" escalation ("bulk
prune skipped: backup stale or absent") for a prune that, in most cities,
would delete nothing. The reaper now first counts the prune's candidates
(closed beads that match the pattern and closed more than
GC_REAPER_SESSION_PURGE_AGEago) and, when there are none, skips the
backup check and the prune without an anomaly. When there are candidates,
the backup-age and type-scope gates apply as before. A count that fails is
reported as its own anomaly (session prune candidate count failed for <db>), and the gates still apply. An invalid pattern is now reported before
the backup is checked (#6284). - Unchanged Dolt backup sync failures and reaper anomalies no longer mail
on every run.escalate.shtakes--incident-keyand--fingerprint:
an alert whose fingerprint matches its last delivered one stays quiet for
GC_ESCALATE_REMINDER_SECONDS(default one day), a changed fingerprint
alerts at once, and both orders pass--resolve-incidenton a clean run so
the next failure alerts at once too. Deduplication is best-effort: when the
state under.gc/incidents/cannot be read or written the alert is sent
anyway, and a failed send is retried on the next run. The reaper's
fingerprint ignores only a measured backup age, so a backup that goes
missing alerts at once. The backup order's escalation reports each
database's first failed attempt instead of its last, with common credential
shapes redacted from every diagnostic. WhenGC_ESCALATE_SEND_TIMEOUT_SECS
cuts a send off beforegc mail sendreports the mail written,
escalate.shnow exits non-zero so callers that check the exit status can
retry it; keyed incidents also retry, because a failed send records no
incident state. A send cut off only after that report still counts as
delivered. The backup order's missing-flockand offsite-publication
alerts still mail on every run, and anescalate.shoverride must accept
or ignore the new flags. One that rejects them as a usage error, as one
written for 1.5.0 does, is sent the alert again without them, so it keeps
alerting, undeduplicated (#6363 and #8231, backported in #8218 and
#8232). - The test suite's startup Dolt orphan sweep no longer deletes a live Dolt
data directory when itslsofscan fails. A scan that hit its 30-second
deadline returned partial output with no error, so directories a running
Dolt server held open looked orphaned and were removed. The sweep now
refuses to delete anything when the scan times out, is killed by a signal,
exits with an unexpected status, or prints no output (#7138, backported in
#7507). This affects only people running the gascity test suite; nogc
command runs this sweep.
Formulas, orders and the API
- The supervisor event stream includes cities started after the client
connected (#6861). Delivery is at-least-once when a city's watcher is
re-attached mid-connection, for example after the city stops and starts
again: events already buffered for that city can be delivered again, and
its seq in the composite SSE id can move back. Dedupe on city and seq. - Terminal steps of the core commit, report and prompt-synthesis formulas
close themselves beforegc runtime drain-ack, so another session can no
longer re-run a step the drain released. After three failed pushes the
commit formula closes its step as a hard failure and leaves the work bead
open with the commits in the checkout (#6992). - Cost estimates price the Claude 5 family and Claude Opus 5.5. The
shipped default price table stopped at the Claude 4.x models, so every
invocation onclaude-opus-5,claude-opus-5-5,claude-sonnet-5,
claude-fable-5orclaude-fable-5-1was recorded unpriced and left out
ofgc costs'EST_USD, which read $0 for a city running those models.
The table now carries Anthropic's list prices for all five. A price is
found only for an exact provider and model match (ignoring case), so
another spelling of the same model, such as a Bedrock model id, still needs
its own[[pricing]]entry incity.tomlorpack.toml; an existing
[[pricing]]entry still outranks the defaults (#6384, #7713, backported
in #8213). POST /v0/city/{city}/slingroutes and cooks formulas exactly like
gc sling: a rig bead's default formula is cooked in the rig store, title
and vars reach the default formula, and a convoy's children are routed one
by one.gc slingagainst a remote city reports a partial convoy failure
like a local one: it prints the failed children,--jsonreports
success: false, and it exits 1.gc sling --onstays refused for a
remote city.- Order history reports
has_outputonly for a run that recorded output,
and an order that runs out of time says so. Order history
(GET /v0/city/{city}/orders/history) sethas_outputon every run of an
exec order, because exec orders capture output, so a run that recorded
nothing advertised output its history detail then served empty;
capture_outputstill describes the order. An order stopped by its
timeout(or the[orders] max_timeoutcap) failed with a bare
context deadline exceededorsignal: killedin itsorder.failed
event, in the controller log for an exec order, and ingc order run's
error; the message now leads with the timeout it ran out of, for example
order timeout of 5m0s exceeded. - An API config edit is no longer lost when it lands while the controller is
reloading config: a load hashes the city's convention trees before reading
them, and a runtime reload can no longer publish an older config over a
concurrent API mutation (ga-opn27h; backport of #7084). GET /v0/city/{city}/patches/...returns the patches declared in
city.tomlinstead of 404/empty after they were applied (ga-8hk1fe).- The run API lists a formula-v1 run's steps and reports the run active once
a step starts. Formula-v1 instantiation types each untyped ortask-typed
non-root stepstep, and the run projection dropped that type. For a v1
run,GET /v0/city/{city}/runs/{id}/stepsreturned no steps, and the run
list,GET /runs/{id}and the run census kept the runpendingwhile its
steps were claimed and closed. GET /v0/city/{city}/sessions?state=closedand?state=alllist closed
sessions. The session list read only open sessions, whatever the filter,
so a filter namingclosedmatched nothing. A filter that can match a
closed session (all, or a list namingclosed) now reads closed sessions
too, reported with stateclosed; they come from the bead store rather than
the cache. The default and every other filter still read open sessions from
the cache.gc session list --state closedand--state alllist them too
when the city's supervisor answers the command.GET /v0/city/{city}/agentsreflects a config change right away instead of
serving a cached list keyed only on session state (ga-79peco).GET /v0/city/{city}/agentsreports a live bounded-pool worker as
running, and no provider for the control dispatcher. The agent list, the
agent read and the rig counts probed a pool slot (<pool>-1) on a session
name derived from the slot, but the controller runs an unaliased pool
session under a name carrying its session bead id (<template>-<beadID>).
Every worker of a pool withmax_active_sessionsset readstoppedwhile
/sessionsshowed it active,?running=truelisted none of them, and a
rig'srunning_countleft them out. When a slot's derived name is not
live, these endpoints now find the slot's session the way/statusdoes
and probe that one. A session whose bead still says active but whose
runtime is gone still reads stopped, and a session store that cannot be
read makes the list partial. The SDK's control dispatcher, which runs
gc convoy control --serveand has no provider, was reported under the
workspace provider, marked unavailable when that provider's binary was not
onPATH, and could take its session's model and context from another
agent's transcript in a shared work dir. It now reports no provider, its
session reads no transcript, and a Claude agent sharing its work dir no
longer loses its model and context on its account. (#4703, backport of
#8211; thanks @remuscazacu)
Security
- Text sent to a Claude session no longer answers its permission prompt.
On Claude Code 2.1.28x gc missed the permission dialog, so
GET .../pendingstayed empty,respondreturned409 no_pending, and an
ordinarysubmitor nudge typed into the prompt, where its Enter approved
the pending tool call. gc now recognizes the dialog by its "Do you want to
…?" question and numbered menu (also when the question wraps), and
respondpicks the option by its label, sodenycan no longer select
"Yes, and switch to auto mode"; when no single option matches it sends
nothing and returns 409invalid_interaction. While a prompt is pending,
POST /v0/session/{id}/submitand.../messagesanswer 409
session-conflictinstead of 202,submitwith intentinterrupt_nowis
refused,gc session nudge --delivery=immediateexits non-zero and
--delivery=wait-idlequeues behind the prompt; the tmux runtime looks
again under the nudge lock, right before it pastes and right before Enter,
and refuses when it cannot look. A Claude session on a permission prompt no
longer reads as idle. Stopping a Claude turn on the tmux runtime (stopor
interrupt_now) now also clears the prompt Claude puts back into the input
box, which can add up to 2 s, and leaves the box alone when a client is
attached. The exec, k8s, ssh and herdr runtimes do not look for the prompt
yet. Thanks to Chris Sells (@csells) (#2892, #6832, backported in #8046). - Go toolchain 1.26.10. Go 1.26.10 fixes GO-2026-6809:
crypto/x509
now refuses the eight compromised eMudhra intermediate CAs (EM DV/OV/EV TLS
CA - G3B/G3C and their Sectigo cross-signs) even where the host trust store
still lists them. Thego.modfloor, the Bazelgo_sdkand the
contrib/k8sGo build images move to 1.26.10, and thegc-mcp-mailimage
takes certifi 2026.10.19 for the same distrust. Building from source now
needs Go 1.26.10 or newer (#8165). - Go toolchain 1.26.9 and
golang.org/x/netv0.60.0. Go 1.26.9 fixes
the 2026-10-08 standard library advisories GO-2026-6599, 6600, 6603, 6604,
6605, 6607, 6608, 6609, 6610, 6611, 6612, 6613 and 6617 innet/http
(HTTP/1 and HTTP/2),html/template,crypto/tls,net/textproto,
mime/multipartandos. x/net v0.60.0 fixes the HTTP/2 ones among them
(GO-2026-6603, 6610, 6611, 6612 and 6617) in thegolang.org/x/net/http2
package thegcbinary links. Thego.modfloor, the Bazelgo_sdkand
thecontrib/k8sGo build images all move to 1.26.9; building from source
now needs Go 1.26.9 or newer (#7387). - OpenTelemetry log modules v0.21.0 fix GO-2026-6615 (the log
BatchProcessorcould busy-spin when its export buffer was full) (#7056). - The
gcbinary linksapache/thriftv0.24.0 (CVE-2026-43871), the
version the container images already forced into thedoltandbdthey
rebuild (#7510, backported in #7621). gc-mcp-mailimage: PyJWT 2.14.0, urllib3 2.8.0 and GitPython 3.1.60.
Fixes CVE-2026-102268 (CRITICAL) and CVE-2026-102266, -102267, -102271,
-102272 and -102273 in PyJWT, CVE-2026-97687 and CVE-2026-97689 in urllib3,
and CVE-2026-87817 and CVE-2026-87819 in GitPython (all HIGH unless noted)
(#7058).gc-mcp-mailimage:fsspec2026.6.0. Fixes CVE-2026-104851 (HIGH,
arbitrary code execution via a crafted URL) in the transitivefsspec
dependency (#7388).- Container images rebuild
gh,doltandbdwithgolang.org/x/net
v0.60.0 (CVE-2026-78669). Thekubectlprebuilt in the controller image
still carries CVE-2026-78669, CVE-2026-78667 and CVE-2026-97031, which no
kubectl release fixes yet; they are waived for that path only, through
2026-11-07 (#7579, backported in #7621). - CodeQL, govulncheck, container-scan, complexity and macOS regression checks
now also run on pull requests tohotfix/**andrelease/**branches
(#7055, backported in #7494).
Known Issues
- On a city using the default bd-owned proxied Dolt, the proxy and its Dolt
server can keep running aftergc stop. Read-only commands on a stopped
city, such asgc session listandgc bd list, can also start them again.
Retiring idle proxied scopes is tracked in #6561. gc doctorcan skip a proxied store's checks without saying the store
might be running. When gc cannot read the proxy's process details (for
example underhidepid, or with a proxy owned by another user), the store
checks of a stopped city reportnot checked: store not runningas passing
(on a running city they warn that the store's proxy is not running). Planned
for 1.5.2.- The Dolt compactor still flattens a database that several cities write
to. Themol-dog-compactororder squashes every commit after its
watermark, whoever wrote it, so the city whose managed Dolt server holds a
shared database, and every city that reaches it over loopback (including a
relay or tunnel on a loopback port), flattens the other cities' history
too. Making compaction safe for shared and cloud-hosted databases is
tracked in #7896 and is not in 1.5.1. On those cities, keep the shared
database out of the order withGC_DOLT_COMPACT_ONLY_DBS, or disable it
([[orders.overrides]]withname = "mol-dog-compactor"and
enabled = false), and compact the shared database by hand with every
writer stopped, as "Shared and non-local databases" in
docs/troubleshooting/dolt-bloat-recovery.mddescribes. - The reaper reports
partialon every run while a subtree is held. On a
city with a permanently held subtree, every reaper run (every 30 minutes)
emits anorder.skippedevent with outcomepartial. This is expected and
not a failure. UnderGC_REAPER_DRY_RUN, the summary reports the prune
preview assessions-pruned:N, although nothing was pruned. Planned for
1.5.2. - Two cities sharing one rig store can still disturb each other's claims
when their agent identities overlap. The controller's pool orphan sweep
and the coreorphan-sweeporder no longer release a claim this city
cannot vouch for (#5959, #5955; see Fixed), but two cities that import the
same agent pack share one identity space: neither sweep can tell a foreign
claim whose assignee matches a local identity byte for byte from a local
one (ga-dk24ao). And when one city runs an agent under an import binding
and the other runs the same agent unbound, each controller's legacy-binding
canonicalization rewrites the other city'sgc.routed_toand assignee back
and forth (#5846). Avoid running agents with the same name in two cities
that share a rig store. - Split storage: a run that wisp GC deletes from a relocated graph store
stays listed in/runs. The #5934 fix recordsbead.deletedfor deletes
made through the controller's bead cache, but a relocated class store has
no such emission layer, so on a split-storage city a completed run burned
by wisp GC keeps its row in the run census. This is the same as 1.5.0. - The blocked-flag repair warns on every start in a legacy gc-managed rig
whose database has uncommitted changes. Some older rig databases carry an
uncommitteddependenciesschema change, and the repair logs "needs a
clean working set" each start (andbd recompute-blockedalone fails
there). Runbd dolt commitin that rig once; the next start repairs it.
Troubleshooting (upgrading to v1.5.1)
| Symptom | Cause | Fix |
|---|---|---|
gc doctor warns packv2-import-state ("pinned at an older gc release's canonical version")
| The city still pins the June 2026 bundled-pack commit. It is served from the binary, so it keeps working | gc doctor --fix
|
locked but not cached after upgrading
| A 1.5.0 supervisor is still running and does not know the new pin | gc supervisor stop --wait, then gc start on the new binary, then gc doctor --fix
|
gc start refuses with storage.binding.unconverged / retained-copies
| The city was migrated to split storage by gc 1.5.0 and still holds frozen copies | gc stop, gc storage migrate --from-work --fleet-stopped, gc start
|
gc init / gc start refuse: the running supervisor is a different gc installation
| Registering would hand the city to the other binary | <this gc> supervisor install --force, stop a directly started supervisor, or pass --allow-supervisor-mismatch
|
Creates in a SQLite bead store fail naming gc storage repair-sequence
| The store's id sequence wrapped under an older build | Stop older builds, then run gc storage repair-sequence (see its help)
|
gc stop prints stop not confirmed and exits 1
| gc could not verify that every session stopped | Fix the reported runtime error and run gc stop again
|
Beads missing from bd ready after a bd upgrade on a running city
| The blocked-flag repair runs only when a city starts | gc restart, or run bd recompute-blocked in that scope
|
gc import upgrade fails: registry pack ... has no release matching "^0.4"
| A 1.5.0 gc import add recorded a range built from a repository tag
| Set version to a range of the listed releases (for example ^0.1), then gc import upgrade
|
An escalate.sh override keeps mailing on every run
| The override rejects the new --incident-key / --fingerprint flags
| Accept or ignore the flags, and send nothing for --resolve-incident
|
Validation
- RC Gate passed on the v1.5.1-rc1 commit. Run
40052710410on
hotfix/1.5.1at039e89f95e. Attempt 1 failed only in CI parity's
packages-runtime-tmux-5-of-6, on a proxy.golang.org stream error during
setup, plus the summary jobs that depend on it, so the downstream tiers
were skipped. Attempt 2 re-ran the failed jobs, and that shard and every
downstream tier passed. Across both attempts the gate covered:- CI parity, including Beads topology acceptance on bd 1.3.2 and the
Windows process tree; - Ubuntu acceptance A, B and C 1–5 with real inference;
- all 29 Ubuntu integration shards and tutorial goldens 1–6;
- every macOS regression job;
- the embedded-pin check and the goreleaser snapshot.
- CI parity, including Beads topology acceptance on bd 1.3.2 and the
- v1.5.1-rc1 release tests: GO. On the rc1 release artifact
(039e89f95e, RC Release run40060825095) with bd v1.3.2 and Dolt 2.2.0:- gascity-packs inference suites passed 10/10 on
claude-opus-5-5
(medium effort): gascity-all, superpowers-all, compound-all, bmad-all,
gastown-all and the superpowers, compound, gstack, bmad and gastown
smokes. - Upgrade from v1.5.0 + bd v1.3.1: the city loaded without
doctor --fix,packv2-import-statewarned and--fixre-pinned it, the
blocked-flag repair corrected exactly the corrupted rows once per scope
and then stayed a no-op, a half-finished workflow completed, and bead
counts were preserved. A legacy gc-managed city from v1.4.2 also
upgraded and migrated to proxied. - Shared-store smoke (two external-Dolt cities, split storage, an exec
provider): foreign claims were kept while a dead local claim was
released; the reaper escalation mailed once, then stayed quiet; an exec
startthat exits 2 failed and was torn down;/runs,
/sessions?state=closed,/agentsand order history reported correctly;
no repeatedbead.updatedevents were seen. - Maintenance orders and doctor (
reaper,jsonl-export,
mol-dog-backup) exited 0 on a proxied city plus a rig, andgc doctor
left a stopped city stopped. - linux_amd64 and linux_arm64 archives passed checksum checks and report
the expected version.
- gascity-packs inference suites passed 10/10 on
- Beads v1.3.2 is a stable release, and Homebrew core
beadsis 1.3.2
(Homebrew/homebrew-core#320852, merged). - The stable tag differs from v1.5.1-rc1 only by the CHANGELOG commit.
v1.5.1(4f698fe2a7) isv1.5.1-rc1(039e89f95e) plus
chore: release v1.5.1(#8260), which moves[Unreleased]to[1.5.1]
inCHANGELOG.mdand touches no other file.
Install
Upgrade bd to v1.3.2 first (see the Upgrading Notes), then install or upgrade
Gas City with Homebrew:
brew update
brew install gascity # new install
brew upgrade gascity # existing install
gc version # 1.5.1brew install gascity resolves to the homebrew-core formula, which picks up
v1.5.1 through Homebrew's automatic version bump, usually within a few hours of
the release. Until brew info gascity shows 1.5.1, use the
gastownhall/gascity tap formula (brew install gastownhall/gascity/gascity),
which already installs v1.5.1, or download the archive below.
Or download the archive for your platform (linux or darwin, amd64 or
arm64) from this release and verify it:
gh release download v1.5.1 -R gastownhall/gascity \
-p 'gascity_1.5.1_linux_amd64.tar.gz' \
-p 'gascity_1.5.1_checksums.txt'
sha256sum --ignore-missing -c gascity_1.5.1_checksums.txt # macOS: shasum -a 256 --ignore-missing -c
tar -xzf gascity_1.5.1_linux_amd64.tar.gz gc
./gc versionThen restart the supervisor on the new binary (gc supervisor stop --wait,
then gc start) and run gc doctor --fix. The release archives carry GitHub
artifact attestations and an SPDX SBOM
(gh attestation verify <archive> -R gastownhall/gascity). To build from
source (Go 1.26.10 or newer), check out the v1.5.1 tag and run
make install.
Contributors
Thanks to everyone who contributed to v1.5.1 (between v1.5.0 and
v1.5.1):
@austinborn, @ckumar1, @csells, @graham-a11y, @jacobhausler, @julianknutsen,
@nonathaj, @quad341, @rjgeng, @sabetsain, @sjarmak, @Toady00
New Contributors
- @sabetsain made their first contribution in #7895
Recommended Reading
- Split storage classes runbook (see "Upgrading a city migrated by gc 1.5.0")
- Troubleshooting guide
- Dolt bloat recovery and history protection
- CLI reference
- Config reference
- Beads v1.3.2 upgrade notes
Full Changelog: v1.5.0...v1.5.1