github gastownhall/gascity v1.5.1
Gas City v1.5.1

4 hours ago

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 core orphan-sweep order no
    longer release claims this city cannot vouch for (#7763, #7895), with the
    new gc agent is-foreign verb as the shared test. Unchanged reaper and
    Dolt backup escalations mail once a day instead of on every run (#6363),
    gc dolt compact no longer looks for a pinned rig's database on the
    city's own Dolt server (#7997), byte-identical bead.updated repeats are
    no longer recorded on every reconcile pass (#7023), an exec provider
    start that 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
    respond picks 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, and gc doctor --fix re-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 from gc pack registry show works, and
    fetched content is verified against the release hash.
  • Supervisor fixes. gc init and gc start refuse 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 and golang.org/x/net v0.60.0, along with
    OpenTelemetry, thrift and container-image security updates.

Upgrading Notes

Upgrade in this order:

  1. Upgrade bd to v1.3.2 (bd version should report 1.3.2). The first
    start of each city afterwards runs bd recompute-blocked once per scope.
  2. Restart the supervisor on the new gc: gc supervisor stop --wait,
    then gc start. This stops every city the supervisor hosts until
    gc start runs. A 1.5.0 supervisor does not know the new bundled-pack
    pin.
  3. Run gc doctor --fix once per city. It clears the
    packv2-import-state warning by re-pinning [imports.core] and
    [imports.bd] offline.
  4. Split-storage cities migrated by 1.5.0 must be re-migrated: gc stop,
    gc storage migrate --from-work --fleet-stopped, then gc 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 a session_setup,
    session_setup_script, session_live or pre_start command 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_timeout is 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 only INT,
    add TERM (e.g. trap rollback INT TERM). Non-shell adapters that relied
    on SIGINT's default action (e.g. Python KeyboardInterrupt/finally) must
    handle SIGTERM explicitly. The bundled scripts/gc-session-docker already
    traps TERM.
  • Upgrade Beads (bd) to v1.3.2. gc 1.5.1 pins and is tested against bd
    v1.3.2 (deps.env BD_VERSION and the go.mod library), a stable release
    that keeps bd v1.3.1's schema: v1.3.1 plus the bd purge live-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-op DOLT_CHECKOUT on fresh connections, so a least-privilege Dolt user
    works with the native store (beads#7322), batched bulk ID reads, so
    bd export and gc's jsonl-export order no longer stall on very large
    wisp sets (beads#7603), bd purge keeping --limit in its confirmation
    hint (beads#7540), and a Go 1.26.10 / x/net / grpc / thrift security
    rebuild. Install it with brew upgrade beads (or brew install beads),
    from the
    v1.3.2 release assets,
    or with go 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, and gc doctor does 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_compat preflight gate), with a gc doctor
    beads-store warning.
  • A city's first start after a bd upgrade runs bd recompute-blocked once
    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 of bd 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 emits beads.blocked.recomputed (a fresh city records one 0-row event
    per scope). The supervisor does the same, and gc rig add repairs the rig
    it adds. It does not touch external Dolt servers, complete storage bindings
    or non-bd providers: run bd recompute-blocked in 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, run gc restart for it
    (or gc stop, then gc start), or restart the supervisor and every city it
    hosts with gc supervisor stop --wait, then gc start. gc start on a
    city that is already running does not repair it (#7087).
  • A city migrated by gc storage migrate in 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 migrate now 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, outcome retained-copies)
    and prints the repair: gc stop, then
    gc storage migrate --from-work --fleet-stopped, then gc start. The same
    run resumes an interrupted clear. gc storage status reports the backup
    and the retained copies and exits non-zero while copies remain. After a
    clear, pointing [storage.classes] back at work (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, and tracks/related edges into moved
    beads, cannot be kept: migrate lists each one before it deletes anything,
    records it in gc storage status and on the storage.binding.* event
    (lost_cross_edges), and keeps it in the backup. Of the six split-city
    issues that 1.5.0's "gc storage migrate is 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-source and reopen-source read 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 migrate is still
    experimental: run gc storage preflight first, 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 init and gc start refuse to register a city with a supervisor
    from a different gc installation.
    When the running supervisor's
    executable is a different file from the invoking gc (for example a
    Homebrew launchd supervisor and a tarball gc), registering would hand the
    city to the other binary. Both commands now refuse and print both
    identities and the fix: <this gc> supervisor install --force for a
    launchd or systemd supervisor, or stop a directly started one. gc init
    has already written the city and tells you to run gc start afterwards.
    Pass --allow-supervisor-mismatch to keep a deliberate mixed setup; gc start then also skips the build-drift restart, which could only relaunch
    the other binary. An in-place upgrade of the same executable never refuses:
    gc start restarts the supervisor on build drift, now also after a
    Homebrew upgrade, where both build ids read unknown and the release
    version decides. gc status only warns, on stderr. The check adds one
    /health call (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 pin gc init writes 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 doctor now reports the old pin as a packv2-import-state warning, and
    --fix re-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 install fetches it, so run gc supervisor stop --wait, then
    gc start, on the new binary first. Stopping the supervisor stops every
    city it hosts until gc start runs. Each superseded pin gets its own
    synthetic cache directory until gc doctor --fix. Downgrading a city
    initialized or re-pinned by 1.5.1 to gc 1.5.0 needs one gc 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 break CALL DOLT_RESET('--hard') on some fresh
    databases, and later releases have not been measured by beads (#7070).
  • gc stop exits 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 stop no longer prints City 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 repeated gc stop of a herdr city whose server is already gone reports
    stop not confirmed and exits 1. Scripts that treat a non-zero gc stop as
    fatal should resolve the reported runtime error and run gc stop again; on
    herdr, once you have confirmed the herdr server is not running, treat that
    exit as stopped (#6764).
  • An exec session provider's start or provision that exits 2 now fails
    the start.
    Exit 2 meant "unknown operation, treat as success" on start
    and provision too, so a set -e wrapper whose grep or jq exited 2 during start
    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 failed start
    runs stop to tear the box down unless the name was already in use. Wrapper authors: return 0 from start and
    provision on 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 naming gc storage repair-sequence. Run it
    without --floor to 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 --json reports "ok": false when a blocking check fails.
    The JSON envelope always said "ok": true. It now matches the exit code:
    false only when blocking_failed is non-zero, true for advisory-only
    failures. Automation that read ok alone 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 show prints failed with no tags ... match constraint.
    Worse, a range such as --version '>=0.1.1', a plain gc import add, and
    gc import install/upgrade of 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 writing pack.toml or
    packs.lock. A plain 1.5.0 gc import add of 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 upgrade fails with
    registry pack main:bmad has no release matching "^0.4" (available: ...),
    and once that run has cached the registry catalog, gc import install fails
    the same way. Set version in 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 carrying gc.step_ref) that
    closes with gc.outcome=pass now satisfies its dependents even when it also
    records gc.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.closed is emitted for every close gc observes. A
    Update(status=closed) now emits bead.closed instead of bead.updated,
    re-closing an already-closed bead no longer re-emits, and orders and
    autoclose now fire for closes made with bd or gc bd (#6860).
  • nudge-on-route no longer retries a failed nudge on a repeated, unchanged
    bead.updated.
    The controller now drops a bead.updated that 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 on bead.updated likewise no longer
    see those repeats (#7023, backported in #8228).
  • API session creates for a demand-only singleton are refused. POST /sessions with kind: "agent" for a pool agent with
    max_active_sessions = 1, min_active_sessions = 0 and no
    [[named_session]] now returns 400 demand-only-singleton instead 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}/sling reports a convoy whose children were routed
    only in part as 200 with status: "partial" and the failed children in
    batch.failures, so API clients must check status, not only the HTTP
    code. A convoy where nothing routed is still an error. A remote gc sling
    older than 1.5.1 does not check status: it exits 0, and its --json
    output reports success: true. Upgrade remote gc clients 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 on null
    fields need updating:
    • Edits to a rig-qualified agent served by a generic scope = "rig"
      template (PATCH, suspend, resume on /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; DELETE on 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=closed or ?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 and Metadata of external-messaging inbound/outbound
      results. The OpenAPI spec no longer lists them as required.
  • 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-local NOW(). It now uses UTC_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 cutoff bd prune uses, so it can no longer miss non-session
    beads bd prune would delete (#7136, backported in #7498).
  • An unchanged reaper or Dolt backup escalation mails once a day, not on
    every run.
    The reaper order (every 30m) and the mol-dog-backup order
    now raise their failures as incidents. escalate.sh records each delivered
    incident under GC_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/*.state file. A pack that overrides escalate.sh now
    receives --incident-key KEY --fingerprint F on an alert and
    --resolve-incident KEY on 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-sequence inspects a SQLite bead store's id-sequence
    floor and, with --floor, raises it (--dir for a store other than the
    city's SQLite infrastructure binding, --prefix, default gcg). See
    Upgrading Notes (#6834).
  • gc storage migrate --from-backup re-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-work refuses there, since the
    work store no longer holds the slice (#7074).
  • --allow-supervisor-mismatch on gc init and gc start registers 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). --json adds the reason
    (roster, session_record, foreign_binding, absent_from_roster,
    no_config, lookup_failed) and its detail. local is 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 a sqlite-beads binding, that ledger's ready read
    returned rows oldest-first (created_at, id). It now returns the canonical
    (priority, created_at, id) order that bd ready and gc's cached ready
    reads already use, and applies Limit after 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/ready also returns the relocated graph leg's rows in
    this order (#6863).
  • gc supervisor start on macOS starts the installed launchd job. When
    the supervisor LaunchAgent is installed and launches this gc, gc supervisor start loads 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; gc forks and names
    <gc> supervisor install --force (#6966, #7048).
  • A slow Dolt identity probe is retried instead of failing gc init and
    gc start.
    The dolt config identity probes (user.name and
    user.email) ran once with a fixed 2s deadline, so a loaded host stopped
    startup with dolt 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. The dolt version and bd version probes, 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/roles rig pack is served from the gascity pack's embedded
    copy.
    gc init with the default gascity template and gc rig add no
    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-days days (default 7), gc import prune --apply
    reclaims it (ga-73eoo, #7005).
  • Tutorial 01's sample city.toml matches what gc init writes, and it
    explains that sha:<pinned commit> on the core and bd imports names the
    copy of each pack bundled with gc (#7045).
  • Config, exec-provider, agent, orders and API reference docs now match the
    code.
    The wisp_ttl example was "7d", which does not parse (Go
    durations have no day unit) and leaves wisp GC off; it is now "168h".
    idle_timeout and max_session_age say they do not apply to
    mode = "always" named sessions, the Gastown recipes stop setting
    idle_timeout on the always-on mayor and show how to suspend boot, and an
    exec order's default timeout reads 300s, not 60s. The exec session
    provider page lists every field start receives, the per-operation
    deadlines, the stop after a failed start and what the session env
    carries, and no longer calls start fire-and-forget, says gc sends the
    startup nudge through the nudge operation, or says the start config
    leaves out ready_prompt_prefix and ready_delay_ms. The agent guide covers
    Claude's builtin permission_mode = "unrestricted" and effort = "max"
    defaults and how to override them; the orders tutorial covers rig = "*"
    and not pairing an override with skip; the API reference says what each
    endpoint's agent count counts and that the supervisor serves its
    spec at /openapi.json, /openapi.yaml and /docs; the events reference
    says the cache-reconcile actor 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 the contrib/ scripts link to pages that
    exist (#8212).

Fixed

Split storage (class bindings)

  • Split storage: gc bd show and gc bd dep list/dep tree no 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 tree of 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> --formula works on a city
    whose graph class is relocated.
    It failed with setting gc.routed_to on gcg-1: ... bead not found and leaked an open, unrouted wisp per attempt;
    the root is now routed through the store that minted it (#6054, #7059).
  • Split storage: gc sling refuses 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 delete and gc convoy delete-source reach
    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 ready reads them. Single-store cities are unchanged (#6019).
  • Fenced writes to relocated classes honor [beads] conditional_writes on
    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. Under auto and require every 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, the require boot preflight probes the binding once per engine,
    and gc status reports 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 default scale_check probe 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 stores city.toml does 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 status reports 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 by gc start: status prints binding: none for 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 starts BEGIN 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_writes at auto or require on 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 the List is reported
    as a conflict, the bead is left as it is, and the next tick decides again.
    require refuses a store without the capability, auto degrades to the
    legacy release, and off (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-sweep order 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 asks gc agent is-foreign about 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
    answers local. Each sweep that keeps claims prints protected K foreign/unknown claims in its summary and a line naming every kept identity
    and its claims, and declares a partial outcome, which the controller
    records as an order.skipped event 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. Where timeout is 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 identities summary 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 core orphan-sweep
    order now applies the same gate through gc 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 with wisp_ttl set, or after GC_REAPER_SESSION_PURGE_AGE (default
    720h) in the city store when the reaper's GC_REAPER_SESSION_BEAD_PATTERN
    (default gm-*) 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-sweep order no longer misses live sessions when the
    session list exceeds the pipe buffer (#6714, #6728).
  • The core orphan-sweep order 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 sets timeout = "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 a timeout (plus rig = "*" 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 row status=closed state=awake that the reconciler and gc session list treated 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, kill or close; a terminal provider error during a
    pending create closes the row as failed-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 name by unique prefix, so stopping an
    already-exited worker-1 killed a live worker-10, and metadata reads,
    send-keys and capture-pane could 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-windows and display-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 to PATH), so
    an older gc in ~/go/bin no 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 in execution_claim_hold as <reason> <time> (#5701).
  • herdr agent starts retry when a fresh pane reports agent_pane_busy on a
    non-zero exit (#6637).
  • A nudge that races an ACP agent's exit is dropped with a stderr line instead
    of failing with file already closed (#6540).
  • Claude Code's feedback-survey dismissal no longer submits a stray 0 into
    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 close runs 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 pin and gc session wake report 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 stop holds the controller lock through bead-store shutdown, and
    gc start --foreground takes it before starting the bead-store
    provider,
    so a racing start or supervisor restart can no longer run a
    controller against a provider that gc stop is retiring, and a racing
    gc start --foreground no longer starts or restarts that provider. A
    supervisor-hosted gc start or gc register that races a gc stop still
    runs the bead-store start before it takes the lock, then waits for the stop
    and starts the city again (#6742, #6789).
  • gc stop stops the city's nudge pollers and never reopens a stopped city's
    store (#6857).
  • Event log rotation no longer loses events. A short-lived gc command
    could rotate events.jsonl from 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.lock sidecar 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 pending in 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, no bead.deleted event 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 as pending,
    and canceling it returned run-not-found. Every delete made through the
    controller's bead cache now records bead.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 purge or gc 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.updated event on every reconcile pass.
    Its bead caches announced
    rows whose state had not changed, so on a busy city most bead.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 a bead.updated whose 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 for nudge-on-route (#7023, backported in
    #8228).
  • Concurrent gc init runs sharing a Dolt database no longer force a
    second initialization over an active migration,
    and a verified-empty
    store is initialized with plain bd init instead of a forced reinit that
    could stop partway through the schema on a loaded host (#6658, #6709).
  • gc dolt compact no 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 --self server,
    [[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-compactor order failed every two hours with Error 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-db cannot
    reach either. A rig whose .beads/config.yaml cannot 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.
    The gc-compact-base watermark 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.md covers keeping a shared
    database out of mol-dog-compactor and a stop-everything gc 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, default gm-*) 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_AGE ago) 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.sh takes --incident-key and --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-incident on 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. When GC_ESCALATE_SEND_TIMEOUT_SECS
    cuts a send off before gc mail send reports the mail written,
    escalate.sh now 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-flock and offsite-publication
    alerts still mail on every run, and an escalate.sh override 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 its lsof scan 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; no gc
    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 before gc 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 on claude-opus-5, claude-opus-5-5, claude-sonnet-5,
    claude-fable-5 or claude-fable-5-1 was recorded unpriced and left out
    of gc 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 in city.toml or pack.toml; an existing
    [[pricing]] entry still outranks the defaults (#6384, #7713, backported
    in #8213).
  • POST /v0/city/{city}/sling routes 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 sling against a remote city reports a partial convoy failure
    like a local one: it prints the failed children, --json reports
    success: false, and it exits 1. gc sling --on stays refused for a
    remote city.
  • Order history reports has_output only for a run that recorded output,
    and an order that runs out of time says so.
    Order history
    (GET /v0/city/{city}/orders/history) set has_output on 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_output still describes the order. An order stopped by its
    timeout (or the [orders] max_timeout cap) failed with a bare
    context deadline exceeded or signal: killed in its order.failed
    event, in the controller log for an exec order, and in gc 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.toml instead 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 or task-typed
    non-root step step, and the run projection dropped that type. For a v1
    run, GET /v0/city/{city}/runs/{id}/steps returned no steps, and the run
    list, GET /runs/{id} and the run census kept the run pending while its
    steps were claimed and closed.
  • GET /v0/city/{city}/sessions?state=closed and ?state=all list closed
    sessions.
    The session list read only open sessions, whatever the filter,
    so a filter naming closed matched nothing. A filter that can match a
    closed session (all, or a list naming closed) now reads closed sessions
    too, reported with state closed; 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 closed and --state all list them too
    when the city's supervisor answers the command.
  • GET /v0/city/{city}/agents reflects a config change right away instead of
    serving a cached list keyed only on session state (ga-79peco).
  • GET /v0/city/{city}/agents reports 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 with max_active_sessions set read stopped while
    /sessions showed it active, ?running=true listed none of them, and a
    rig's running_count left them out. When a slot's derived name is not
    live, these endpoints now find the slot's session the way /status does
    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 --serve and has no provider, was reported under the
    workspace provider, marked unavailable when that provider's binary was not
    on PATH, 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 .../pending stayed empty, respond returned 409 no_pending, and an
    ordinary submit or 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
    respond picks the option by its label, so deny can no longer select
    "Yes, and switch to auto mode"; when no single option matches it sends
    nothing and returns 409 invalid_interaction. While a prompt is pending,
    POST /v0/session/{id}/submit and .../messages answer 409
    session-conflict instead of 202, submit with intent interrupt_now is
    refused, gc session nudge --delivery=immediate exits non-zero and
    --delivery=wait-idle queues 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 (stop or
    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. The go.mod floor, the Bazel go_sdk and the
    contrib/k8s Go build images move to 1.26.10, and the gc-mcp-mail image
    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/net v0.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 in net/http
    (HTTP/1 and HTTP/2), html/template, crypto/tls, net/textproto,
    mime/multipart and os. x/net v0.60.0 fixes the HTTP/2 ones among them
    (GO-2026-6603, 6610, 6611, 6612 and 6617) in the golang.org/x/net/http2
    package the gc binary links. The go.mod floor, the Bazel go_sdk and
    the contrib/k8s Go 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
    BatchProcessor could busy-spin when its export buffer was full) (#7056).
  • The gc binary links apache/thrift v0.24.0 (CVE-2026-43871), the
    version the container images already forced into the dolt and bd they
    rebuild (#7510, backported in #7621).
  • gc-mcp-mail image: 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-mail image: fsspec 2026.6.0. Fixes CVE-2026-104851 (HIGH,
    arbitrary code execution via a crafted URL) in the transitive fsspec
    dependency (#7388).
  • Container images rebuild gh, dolt and bd with golang.org/x/net
    v0.60.0
    (CVE-2026-78669). The kubectl prebuilt 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 to hotfix/** and release/** 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 after gc stop.
    Read-only commands on a stopped
    city, such as gc session list and gc bd list, can also start them again.
    Retiring idle proxied scopes is tracked in #6561.
  • gc doctor can skip a proxied store's checks without saying the store
    might be running.
    When gc cannot read the proxy's process details (for
    example under hidepid, or with a proxy owned by another user), the store
    checks of a stopped city report not checked: store not running as 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.
    The mol-dog-compactor order 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 with GC_DOLT_COMPACT_ONLY_DBS, or disable it
    ([[orders.overrides]] with name = "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.md describes.
  • The reaper reports partial on every run while a subtree is held. On a
    city with a permanently held subtree, every reaper run (every 30 minutes)
    emits an order.skipped event with outcome partial. This is expected and
    not a failure. Under GC_REAPER_DRY_RUN, the summary reports the prune
    preview as sessions-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 core orphan-sweep order 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's gc.routed_to and 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 records bead.deleted for 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
    uncommitted dependencies schema change, and the repair logs "needs a
    clean working set" each start (and bd recompute-blocked alone fails
    there). Run bd dolt commit in 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 40052710410 on
    hotfix/1.5.1 at 039e89f95e. 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.
  • v1.5.1-rc1 release tests: GO. On the rc1 release artifact
    (039e89f95e, RC Release run 40060825095) 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-state warned and --fix re-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
      start that exits 2 failed and was torn down; /runs,
      /sessions?state=closed, /agents and order history reported correctly;
      no repeated bead.updated events were seen.
    • Maintenance orders and doctor (reaper, jsonl-export,
      mol-dog-backup) exited 0 on a proxied city plus a rig, and gc doctor
      left a stopped city stopped.
    • linux_amd64 and linux_arm64 archives passed checksum checks and report
      the expected version.
  • Beads v1.3.2 is a stable release, and Homebrew core beads is 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) is v1.5.1-rc1 (039e89f95e) plus
    chore: release v1.5.1 (#8260), which moves [Unreleased] to [1.5.1]
    in CHANGELOG.md and 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.1

brew 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 version

Then 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

Recommended Reading

Full Changelog: v1.5.0...v1.5.1

Don't miss a new gascity release

NewReleases is sending notifications on new releases.