If
gc start fails after install, use the
gc start failure walkthrough to
match the final FATAL: line to the likely cause and resolution.Run the Built-in Doctor
gc doctor checks your city for structural, config, dependency, and runtime
issues. It is always the best first step:
Add City-Local Doctor Checks
Use[[doctor.check]] in city.toml for a workspace-specific health check
that does not need to be packaged as a reusable pack doctor. Provide the bare
check name; gc doctor adds the local: prefix in output.
script and optional fix paths are relative to the city root. Absolute
paths and paths that escape the city directory are rejected and reported as
named StatusError check results.
Local checks reuse the same script protocol as pack doctor checks:
The first stdout line becomes the check message. Additional stdout lines are
shown by
gc doctor --verbose.
”does not import required builtin pack(s)” Warning
Builtin packs compose only through explicit pinned[imports] in
pack.toml — nothing splices them into config composition implicitly.
gc init writes the imports (plus a matching packs.lock) for new cities:
bd entry is written only for bd-provider cities, the default;
non-bd providers get only core.)
If a required import is missing — typically in a city created before the
imports became explicit — config load still self-heals the user-global pack
cache and prints a once-per-city warning:
builtin-pack-imports doctor check migrates the city to the imports
model: it strips legacy workspace.includes entries pointing at the retired
per-city .gc/system/packs tree, adds the missing pinned import(s) to
pack.toml, and refreshes packs.lock and the cache. Leftover
.gc/system/packs directories on disk are pruned automatically.
gascity-pack-binding Doctor Warning
Cities created by gc v1.4.x import the public Gas City pack under the key
gascity:
gc: the pack documents skill gc.mayor, and
role prompts in Gas City pack releases after 0.1.6 run gc gc claim. Under
[imports.gascity] that command is gc gascity claim, so role workers fail
their first claim once the pack is bumped past 0.1.6. Current gc init writes
[imports.gc].
gc doctor reports this as a warning; the city keeps working at the pinned
0.1.6 pack. Run:
gascity-pack-binding check renames the key to [imports.gc] in
pack.toml (or in a city.toml root [imports] override), keeping the source,
version, and every other field. packs.lock is keyed by source, so no
reinstall is needed. The check identifies the pack by its source (any https,
http, or SSH spelling of gastownhall/gascity-packs with the gascity pack
directory), not by the key name, so a fork or a different pack bound as
gascity is left alone, and it never
touches rig imports or [defaults.rig.imports].
The fix changes nothing and explains why when:
[imports.gc]already imports a different pack. Rename one of the imports by hand.[imports.gc]already imports the Gas City pack with a different version or settings. Remove one of the two imports by hand. An exact duplicate (same source and version) is removed automatically.- A string value in
pack.tomlorcity.tomlis shaped like agascity.-qualified name, for example a patch targetinggascity.<agent>. The message names the file and key. Update the value togc.and rerun, or rename the import by hand if the value is unrelated. Comments are ignored.
”command not found” After Install
Ifgc is installed but your shell cannot find it, the binary is not on your
PATH.
Homebrew puts binaries in a directory that is usually already on your PATH.
Run brew --prefix to confirm, then check that $(brew --prefix)/bin appears
in your PATH.
Direct download requires you to move or symlink the binary into a
directory on your PATH:
~/.bashrc or ~/.zshrc.
Oh My Zsh Git Plugin Hides gc
Oh My Zsh’s git plugin defines gc as an alias for
git commit --verbose. When that alias is active, commands like gc version,
gc init, or gc start run git instead of the Gas City binary.
Temporary workaround:
command bypasses shell aliases for that invocation.
Persistent fix in ~/.zshrc:
unalias line must come after Oh My Zsh loads. If it appears before
source "$ZSH/oh-my-zsh.sh", the git plugin recreates the alias later.
Oh My Zsh also loads files in $ZSH_CUSTOM after built-in plugins, so this is
a good alternative:
git from the
plugins=(...) list.
Missing Prerequisites
gc init and gc start check for required tools and report any that are
missing. You can also run gc doctor inside an existing city for a fuller
check.
Always required
Required for the default beads provider (bd)
Optional for GitHub gates
Gas City can run without
gh. The core pack’s maintenance orders skip
GitHub gate checks when the GitHub CLI is not installed.
If you do not want to install dolt, bd, and flock, switch to the file-based
store:
city.toml:
bd provider adds
durable versioned storage and is recommended for real work.
Dolt Version Too Old
Gas City requires a final Dolt 2.1.0 or newer. Older and pre-release builds are below the managed bd/Dolt compatibility floor; releases before 1.86.2 can also miss the upstream GC/writer deadlock fix in dolthub/dolt commitccf7bde206, which can hang dolt_backup sync under heavy write load. Check
your version:
brew upgrade dolt) or download a newer release from
dolthub/dolt/releases.
bd Version Too Old
Gas City requires bd 1.0.4 or newer. The bd-backed store relies on
ephemeral-bead support used by order tracking, including bd create --ephemeral and bd query ephemeral=true, so older binaries can fail
order tracking and the cleanup of those ephemeral beads. Check your
version:
brew upgrade beads) or download a newer release from
gastownhall/beads/releases.
Native Store Falls Back Because Hooks Are Installed
Nativebd store selection intentionally falls back to the subprocess-backed
store when executable .beads/hooks/on_create, .beads/hooks/on_update, or
.beads/hooks/on_close scripts are present. Those hooks historically emitted
bead events for external bd writes; the native in-process store does not run
shell hooks.
For orchestrator-managed Gas City deployments, confirm that the orchestrator is
wrapping stores with CachingStore and emitting bead.created,
bead.updated, bead.closed, and bead.deleted events to the event bus. After
that migration is verified, remove the executable hook scripts from the city or
rig .beads/hooks/ directory to allow native store adoption.
While a city still depends on those hook scripts directly, keep its stores on
the subprocess-backed store by turning native transport off in city.toml:
gc commands, and the stores the city opens for a
single tick, read the current city.toml every time. This setting replaces
the GC_BEADS_FORCE_FALLBACK=1 environment variable, which still works but is
deprecated and applies to every city the process serves. With either one,
gc start refuses a [storage] binding served by beads-workspace, because
that provider opens the native store. Turn native transport off only in cities
that have no such binding, or remove the binding first.
Native Store Falls Back Because Dolt Is in Embedded Mode
Symptom:gc status or gc session list is slower than expected, or
gc doctor reports native_store_unavailable gate=dolt_mode_safe.
Gas City’s native in-process beads store requires that bd context reports
dolt_mode=server. When bd is configured with an embedded Dolt instance (the
default for a freshly installed bd), the dolt_mode_safe gate fails and Gas
City falls back to invoking the bd CLI as a subprocess for every store
operation. Each subprocess call adds tens to hundreds of milliseconds of
overhead, which accumulates noticeably during gc status and gc session list.
Remedy: Run bd against a Dolt SQL server so that bd context reports
dolt_mode=server:
bd context reports dolt_mode=server, gc doctor will clear the
dolt_mode_safe gate and Gas City will use the native store automatically
on the next start.
The
gascity_native_beads build tag visible in some source files is a
test-only mechanism — it requires GC_NATIVE_DOLTLITE_BEADS=true and is
not a supported operator path. Use Dolt server mode instead.dolt_mode_safe Preflight Gate Fails
The native in-process store is unavailable after gc start, and the supervisor
log shows:
gc status --json | jq .beads reports "preflight_gate":"dolt_mode_safe" with
"native_store_eligible":false.
The dolt_mode_safe gate keys off the dolt_mode value that bd context
reports; a value of embedded fails the gate. The gate reads this from
bd context, not from .beads/config.yaml, so hand-editing the config file is
not the supported repair. Confirm what bd currently reports:
bd against a Dolt SQL server so bd context
reports dolt_mode=server, then apply and re-check:
dolt_mode_safe check — it guards the store-mode
contract that keeps bd and gc in agreement.
Listings Miss Durable Beads After Changing types.infra
Symptom: after a type is added to a store’s types.infra setting, a
running city’s listings of that type return only its ephemeral beads.
The native store asks the server to filter a listing by type only for types
the server keeps durable, and it reads which types those are from the store’s
stored types.infra setting when it opens. A running city keeps what it read
for the stores it holds open. gc commands, and the stores the city opens for
a single tick, read the current setting every time.
Remedy: change the stored setting, then restart the city:
types.infra in its own .beads/config.yaml, put the same
types in the stored setting too.
flock Not Found (macOS)
macOS does not shipflock. Install it via Homebrew:
Cursor MCP Tools Still Prompt or Appear Unavailable
The built-incursor provider starts cursor-agent with -f --trust so an
unattended worker does not stop at Cursor’s workspace-trust dialog. Use it only
for workspaces whose contents you trust. The flag does not approve MCP servers;
Cursor’s MCP approval prompt remains enabled by default.
For unattended Cursor pool workers, opt in only after confirming that every
workspace and user/global MCP server visible to Cursor is trusted. The
--approve-mcps flag approves every visible server, including servers projected
from Gas City’s catalog into .cursor/mcp.json and servers from
~/.cursor/mcp.json.
args directly, the override replaces the built-in
args. Include -f --trust yourself and add --approve-mcps only for the same
explicit MCP trust decision. Agent-level args overrides behave the same way.
Existing Cursor sessions keep the command fingerprint they were created with.
The supervisor reconciler restarts sessions automatically after the fingerprint
changes. Drain the pool first when you need a controlled handoff rather than
waiting for the next automatic restart.
gc version Prints Unexpected Output
If gc version prints git progress lines (Enumerating objects...) instead
of a clean version string, upgrade to Gas City v0.13.4 or later. This was a
bug where remote pack fetches wrote git sideband output to the terminal,
fixed in PR #141.
Provider Credentials Dropped When the Supervisor Starts
Symptom: agents authenticate fine when you launch a city from your normal interactive shell, but fail to authenticate (or silently fall back to a different provider) when the city is started by the supervisor at login or after a reboot. Cause: the supervisor service file (launchd plist / systemd unit) captures provider credentials by snapshotting the environment of the shell that rangc start (or gc supervisor install). A credential that is only present
in an interactive shell — for example sourced from an rc file that the login
service manager never reads — is not in that snapshot, so it never reaches
the supervised process.
Fix: put the durable credentials in a machine-local secrets file at
${GC_HOME}/secrets.env (defaults to ~/.gc/secrets.env). On every service
file regeneration, gc merges this file into the supervisor environment, so
the value survives a reboot regardless of which shell ran gc start.
KEY=VALUE per line, # comments, blank lines,
an optional export prefix, and optional surrounding quotes. Only keys that
are already eligible for the supervisor environment are merged — provider
credentials (recognized by their standard prefixes such as ANTHROPIC_,
OPENAI_, GEMINI_), a short built-in list of settings (among them the Dolt
credential and logging settings GC_DOLT_USER, GC_DOLT_PASSWORD, and
GC_DOLT_LOGLEVEL, and the beads pool deadlines below), plus any keys you opt
in via GC_SUPERVISOR_ENV; any other key in the file is ignored. A value
exported in the calling shell still takes precedence over the file, and
GC_SUPERVISOR_OMIT_PROVIDER_CREDS=1 suppresses provider credentials from both
sources.
The same file is the durable home for the beads connection-pool deadlines.
gc reads BEADS_DOLT_POOL_READ_TIMEOUT and BEADS_DOLT_POOL_WRITE_TIMEOUT
for its own store connections only from its process environment; the
dolt.pool-*-timeout keys in .beads/config.yaml and entries in .beads/.env
reach the bd CLI, not gc. If the 10-second default is too tight for a
loaded shared Dolt server (store operations fail with i/o timeout or
invalid connection), raise both here:
bd commands and agent sessions that inherit the
supervisor’s environment, where they take precedence over the per-workspace
.beads/ settings.
Apply the change by regenerating the service file:
A Custom Environment Variable Doesn’t Reach Agent Sessions
Symptom: a non-GC_-prefixed variable you’ve exported and confirmed is set
(e.g. in your shell, ~/.bash_env, or a systemd/launchd unit) never shows up
inside a spawned agent session’s environment, even though gc supervisor run itself can see it.
Cause: passthroughEnv only forwards a variable into a session if it is
either GC_-prefixed, part of the small fixed provider/locale/XDG set, or
named in GC_SUPERVISOR_ENV — the same opt-in gc supervisor install uses to
widen the persisted service-file env (see above). Everything else is dropped
silently, by design: an unbounded sweep of the calling environment would leak
whatever secrets happen to be sitting in the supervisor’s process, into every
agent session.
Fix: name the variable in GC_SUPERVISOR_ENV in the environment the
supervisor daemon itself runs with, then restart it so the daemon process
picks up both the opt-in list and the variable’s value:
GC_SUPERVISOR_ENV itself needs to be present in the supervisor daemon’s own
environment for this to survive a later restart — it is not automatically
persisted into the generated service file the way PATH/GC_HOME are. If
you rely on a managed service file, either list GC_SUPERVISOR_ENV among its
own opted-in names (GC_SUPERVISOR_ENV=GC_SUPERVISOR_ENV,MY_CUSTOM_VAR) or
set it directly in the unit’s Environment= lines.
For a value that’s the same on every city, [workspace.env] in city.toml is
usually simpler than an opt-in — it doesn’t depend on the supervisor’s own
process environment at all:
Supervisor Log Written Twice (journald + supervisor.log)
gc supervisor run tees its output into ${GC_HOME}/supervisor.log
(defaults to ~/.gc/supervisor.log) so gc supervisor logs works no matter
how the supervisor was started. Under a hand-managed systemd unit with
StandardOutput=journal, that tee becomes a second copy of every line:
journald keeps one, and supervisor.log grows without rotation.
Set GC_SUPERVISOR_LOG_TEE=0 in the supervisor’s environment to disable the
tee so the service manager’s log is the single sink. Only the literal value
0 disables it; any other value (or unset) keeps the default tee.
-
The variable matters in two places. The supervisor process’s
environment controls the tee. The shell running
gc supervisor logscontrols only what that command reports: when the variable is set there andsupervisor.logexists, the file is tailed with a staleness warning; when the file is absent, the command points at the service manager’s log (journalctl --user -u gascity-supervisor.serviceon Linux) instead. A unit’sEnvironment=lines are invisible to your interactive shell, so export the variable in both for coherent behavior. -
Service files generated by
gc supervisor installorgc startdo not need — and do not honor — the opt-out. Generated units redirect supervisor output straight intosupervisor.log(systemdStandardOutput=append:, launchdStandardOutPath), and the tee already suppresses itself when its output is that same file, sosupervisor.logis the single sink in those shapes. The variable is not captured into generated service files automatically; it exists for units you manage by hand. -
To persist the variable into a generated service file anyway — for
example as a starting point you then hand-edit to
StandardOutput=journal— opt it in explicitly and regenerate:Note thatgc startregenerates the service file with the file-redirect defaults, so a hand-edited unit at gc’s service path stays journal-only only on hosts where gc never manages the unit.
Delegating the Supervisor Lifecycle to an Operator-Managed systemd Unit
By defaultgc owns the supervisor lifecycle: gc start installs and
starts a per-user service (gascity-supervisor), and binary-drift
detection restarts that service directly. Hosts that run the supervisor
under an operator-managed systemd unit instead — for example a hardened
system service with its own restart policy — can delegate the lifecycle:
gc supervisor startand thegc startensure path runsystemctl [--user] start <unit>(bounded, so a wedged unit cannot hold the CLI indefinitely) and wait for the control socket to answer. When the socket stays unreachable — the usual situation for a system-scope unit running under a different user — start falls back to the same liveness evidencegc supervisor statustrusts: an active unit, then the supervisor HTTP API. Only when all three are silent does start fail. gc never writes, loads, or daemon-reloads its own service files in delegated mode;gc supervisor installrefuses to run, andgc supervisor uninstallonly removes gc’s own legacy service.gc supervisor stoprunssystemctl [--user] stop <unit>synchronously, bounded by--wait-timeout(default 30s) whether or not--waitis set, then verifies a previously-running supervisor actually exited. A live supervisor the unit does not manage (common mid-migration) fails the stop with its PID instead of reporting a false “Supervisor stopped.”, and stop with nothing running keeps the legacy exit-1 “supervisor is not running” contract.- The
gc startdrift auto-restart runssystemctl try-restart <unit>(a unit the operator stopped stays stopped) and fails unless the restart verifiably resolved the drift: a supervisor that was not replaced, a replacement still serving the drifted build (the unit’sExecStartlaunches a stale binary), or an unverifiable post-restart probe each fail instead of declaring “ready” while a stale supervisor keeps serving. gc supervisor statusprobes the delegated unit (systemctl [--user] is-active <unit>) when the control socket is unreachable — the usual situation for a system-scope unit running under a different user — and reports a broken delegation config (a warning in text mode, aconfig_errorfield in--json) instead of a bare “not running”.
GC_SUPERVISOR_SYSTEMD_SCOPE value is a hard error on every
lifecycle path; gc never silently falls back to the default unit.
Setting GC_SUPERVISOR_SYSTEMD_UNIT on a non-Linux platform is the
same kind of hard error — delegation is a systemd contract.
Supervisor Does Not Come Back After Logout (macOS launchd Agent Disabled)
Symptom: on macOS the supervisor is running, but it does not come back after logout or reboot, andgc supervisor status prints a warning on stderr:
gc supervisor stop disables the launchd agent so the stopped
supervisor stays stopped, even across logins. That override is persistent: it
survives logout and reboot, and only launchctl enable clears it. A
supervisor started any other way afterwards, such as a foreground
gc supervisor run, passes every liveness check while launchd would never
restart it. An installed agent that is not loaded has the same effect, and
status warns about it too (... is not loaded; it will not restart if the supervisor exits). To see the override yourself:
--json, status adds "supervised": false and a supervision_warning
string carrying the same message. Both keys are absent when launchd is
supervising, and also when status cannot tell: no agent is installed, its
plist cannot be read, or launchd does not answer.
Fix: run the command the warning names. It depends on what status found:
Use
stop --wait: a plain gc supervisor stop returns while the supervisor
is still shutting down, and gc supervisor start refuses while a supervisor
is running. A plain gc supervisor install is not a substitute either: when
the service file is already current and a supervisor is running, it reports
success without enabling the agent.
JSONL Archive Push Failures
The core pack runsjsonl-export every 15 minutes to export each bead
store (the city and every rig) with bd export into a text-diffable JSONL
snapshot inside a local git repository (the “JSONL archive”): one issue per
line, with its labels, dependencies and comments. The archive serves as a
disaster-recovery backup: a snapshot from any commit restores with
gc bd import <file>.
jsonl-export (every 15 minutes) and reaper (every 30 minutes) ship in
the core pack, so they are active in every city by default — including
cities that previously ran them only via the opt-in gastown maintenance
pack. Both reach every bead store through gc bd, so they work the same on
bd-owned proxied, gc-managed and mixed cities. On cities whose beads
provider is not bd (for example [beads] provider = "file"), both orders
skip with a one-line message and an order.skipped event instead of
running. To turn them off entirely, skip them by name in city.toml:
mol-dog-jsonl,
mol-dog-reaper) stay opted out; the renamed orders honor the legacy
skip entries.
Local-only vs push mode
The archive operates in one of two modes, detected from the state of its git remotes on every run:- Local-only (default). No
originremote is configured. Commits are created and retained on the host but never leave the machine. This mode is safe to run indefinitely; its only limitation is that the archive is not backed up off-box, so a disk failure on this host loses the archive alongside the live Dolt data. - Push. An
originremote is configured. Each run rebases ontoorigin/mainand pushes new commits so the archive survives a host loss.
jsonl-export logs the active mode to stderr on transitions
(e.g. after you add or remove origin) and re-logs it at least weekly so
that an operator reading the log file can always find the current mode.
Archive object maintenance
Every snapshot commit creates loose Git objects, including in local-only mode. The exporter checks the archive before each export and after each new commit. It runs a foreground, low-prioritygit gc when either of these
defaults is reached:
GC_JSONL_GC_LOOSE_OBJECTS=2000GC_JSONL_GC_LOOSE_KB=262144(256 MiB)
GC_JSONL_GC_WINDOW_MEMORY=256m (a positive byte count, without leading
zeros, with an optional k, m, or g suffix in either case) to avoid
competing with the Dolt server and worker sessions for memory. These values
can be lowered for small hosts.
Packing writes a new copy of the archive’s objects before it deletes the old
ones. It starts only when the archive’s filesystem can hold that copy and
still keep GC_JSONL_GC_MIN_FREE_KB=512000 (500 MiB) free; otherwise the
order fails with a message giving the free space. The check counts loose
objects at their full size, though snapshots usually pack to a small fraction
of it: one archive’s 9.01 GiB of loose objects packed to 67.40 MiB. So an
archive with a large backlog of loose objects can be refused on every run
even when packing it would fit. To recover, pack it once by hand with the
command the failure message prints (or the lower-memory one below), free disk
space, or set GC_JSONL_GC_MIN_FREE_KB=0 to disable the check. If df
cannot report the free space, packing goes ahead with a warning.
An invalid setting fails the order before it exports anything. A refused
free-space check or a failed git gc fails the order visibly, and later runs
export no new snapshots until packing succeeds. Snapshots already committed
still reach origin: a run packs only after its push, and a run that finds
the archive cannot be packed pushes any waiting snapshot before it stops. A
spike halt sends its alert, or saves it for retry, before it packs.
To inspect the archive without changing it:
Enabling off-box backup
Pick a repository that only this host will push to (the archive contains bead content and should not be shared across cities). Then:jsonl-export detects the new origin,
logs archive running in push mode, and resumes pushing every run.
On cities migrated from the gastown maintenance pack, the archive stays
at its legacy location — .gc/runtime/packs/maintenance/jsonl-archive
(or .gc/jsonl-archive for pre-pack cities) — and the
packs/core/jsonl-archive path above does not exist. Point ARCHIVE at
the legacy path instead; gc doctor reports the resolved archive path
for the city.
Switching back to local-only
Remove the remote:archive running in local-only mode. If push mode had accumulated failures before the remote was
removed, local-only detection clears that stale failure counter while
retaining pending_archive_push so deferred commits are still pushed if
origin returns.
Reading a JSONL push failed [HIGH] escalation
When push mode is active and git push fails GC_JSONL_MAX_PUSH_FAILURES
times in a row (default: 3), the default human escalation mailbox receives an
ESCALATION: JSONL push failed [HIGH] message with a body shaped like:
GC_JSONL_PUSH_RETRY_DELAY_MIN to change the lower bound and
GC_JSONL_PUSH_RETRY_DELAY_SPAN to change the random span added above that
minimum.
The exporter sends one HIGH escalation for a still-unresolved push
failure. It continues recording consecutive_push_failures and
pending_archive_push in state, but does not mail the same failure on
every tick. A successful push or a switch back to local-only mode clears
the escalation marker.
Maintenance escalation and completion routing
Core maintenance scripts route alerts through a generic escalation hook instead of mailing a hardcoded role. Orders inherit the orchestrator’s environment, so set these at orchestrator start to customize routing:GC_ESCALATION_RECIPIENT— mail recipient for escalations (default:human, the reserved human mailbox). An agent recipient is woken with--notify; thehumandefault is a mailbox with no session behind it, so nothing is woken and the escalation waits until somebody reads that inbox. Point this at the agent that surfaces alerts (the manager, on a Slack-connected city) if you want maintenance advisories acted on rather than filed.GC_ESCALATE_SCRIPT— absolute path to an escalation script to run instead of searching packs.GC_ESCALATE_SEARCH_PACKS— space-separated pack names searched (in order) for anassets/scripts/escalate.shoverride (default:gastown maintenance bd core). A pack earlier in the list wins.GC_ESCALATE_SEND_TIMEOUT_SECS— wall-clock bound on one escalation send (default: 30). The wake can outlive the send it follows, and escalations run inline in maintenance orders, so the bound keeps a slow wake from stalling the run that raised the alarm.gc mail sendprintsSent messageonce the mail is written, before the wake. A bound that trips after that line costs at most the wake, never the message, and the script still exits 0. A bound that trips before it means the mail may not exist, for any recipient: the script exits non-zero and the next run sends again.GC_ESCALATE_REMINDER_SECONDS— how long an unchanged incident stays quiet before it is mailed again (default: 86400, one day). See Why a maintenance alert did not repeat.GC_ESCALATE_INCIDENT_DIR— where incident state is kept (default:<city>/.gc/incidents).GC_ESCALATE_NOW_EPOCH— test-only: pins the script’s clock. Leave it unset; a fixed clock never reaches the reminder interval, so an unchanged incident is never mailed again.GC_MAINTENANCE_DONE_TARGET— session target to nudge withMAINTENANCE_DONE:/warn summaries when a maintenance run completes (default: unset, no completion nudge). Deployments that relied on the old hardcoded completion nudges to a health-patrol session should set this to restore that loop.
- Credentials rotated or expired. SSH key removed from the remote
host, HTTPS token expired. The captured stderr usually reads
Permission denied (publickey)orremote: Invalid username or password. - Remote URL typo or deleted repo. stderr reads
does not appear to be a git repositoryorrepository not found. - Network partition. stderr reads
Could not resolve hostor a connection-timeout message. If the host is also firewalled from the rest of the internet, this will recover once connectivity returns. - Diverged history. Very unusual — the archive rebases onto
origin/mainautomatically — but if the remote was force-pushed from another host, rebase may fail with a conflict. Inspecting the archive and resolving manually is the only option.
GC_JSONL_MAX_PUSH_FAILURES=99 in the orchestrator’s environment and
restart the city with gc restart. That bumps the escalation threshold
from 3 to 99, which at the current 15-minute tick rate is ~24 hours of
silence.
Why a maintenance alert did not repeat
A maintenance order that keeps finding the same problem would otherwise mail it on every run: 48 times a day for the 30-minutereaper order. So two
orders raise their failures as incidents: an alert that is unchanged since
it was last mailed stays quiet for GC_ESCALATE_REMINDER_SECONDS (default:
one day). A change in what failed mails at once, and a clean run resolves the
incident, so the next failure mails at once too.
A quiet run says so in its output:
flock and offsite-publication alerts are not
incidents and still mail on every run.
Deduplication is best-effort. When the incident state cannot be read or
written, the alert is mailed anyway, and a send that fails is not recorded,
so the next run sends it again. To make the next run mail an incident at
once, delete its state file. From the city directory:
escalate.sh 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 that rejects them loses the alert.
WSL (Windows Subsystem for Linux)
Gas City works under WSL 2 with a standard Ubuntu or Debian distribution. Install prerequisites using the Linux column in the tables above. tmux requires a working terminal — use Windows Terminal or another WSL-aware terminal emulator.Build From Source Fails
Building from source requiresmake and Go 1.26.4 or newer:
make is missing, install it (apt install make on Debian/Ubuntu, or
xcode-select --install on macOS). If your Go version is too old, update it
from go.dev/dl or via your package manager. Then:
Slung Beads Not Reaching Agents (managed-city mode)
Ifgc sling accepts work but agents don’t process it — especially if
your supervisor log shows rigStores=0 or assignedWorkBeads=0, or
your bd dolt set port edits keep reverting at the next gc start —
you’re likely looking at a rig whose Dolt view has drifted from the
managed city Dolt. Do not edit .beads/dolt-server.port or
bd dolt set port directly; both self-revert.
See the
Managed-city Dolt endpoints runbook
for the mental model, the forbidden edits, the sanctioned escape
hatches (gc rig set-endpoint --inherit/--self --force/--external),
and an end-to-end recovery recipe.
Still Stuck?
If a symptom only makes sense once you know how the pieces fit together, see The six primitives for the underlying model. Open an issue at gastownhall/gascity/issues with the output ofgc doctor --verbose and your OS/architecture.