{"slug": "we-used-gas-town-to-build-a-wasteland-an-honest-remediation-gastown-log", "title": "We Used Gas Town to Build a Wasteland – an honest remediation gastown log", "summary": "A developer built Wasteland Wire, a working Mad Max fan site that aggregates real headlines from public RSS feeds, using the Gas Town multi-agent orchestration CLI to dispatch a single Claude-powered worker. The trial ran one task with one executing worker and no merge queue, and the writeup documents failed worker-start attempts alongside the successful run, noting that durable task state in Beads/Dolt is not durable model memory and that a separate worktree or tmux session is not a security sandbox.", "body_md": "# We Used Gas Town to Build a Wasteland\n\n*A hands-on trial of multi-agent tooling, one actual coding agent, and a surprisingly good Mad Max fan website.*\n\nThe prompt was simple: **let's try Gas Town by building a Mad Max fan website that aggregates post-apocalyptic news.**\n\nThe result was **Wasteland Wire**: an unofficial fan site with oversized condensed typography, a rust-and-sand palette, and an original road-to-the-sunset illustration. Underneath the wasteland aesthetic, it pulls real headlines from public RSS feeds. No invented articles. No decorative fake news cards.\n\n**Try the live site:** [wasteland-wire.onrender.com](https://wasteland-wire.onrender.com) · **Source:** [bdmorin/wasteland-wire](https://github.com/bdmorin/wasteland-wire). It runs on Render's free tier, so an idle instance can take about a minute to wake.\n\nThe website turned out well. Getting the agent orchestration running was less straightforward. This post records both sides of that experience—without treating workarounds as fixes.\n\n## First: what does Gas Town actually do?\n\nAt one point during setup, the obvious question came up: **is `gt` just a proxy that executes Claude?**\n\nNot quite. Claude does the coding; Gas Town organizes the work around it.\n\nGas Town's Go CLI creates managed project repositories called **rigs**, allocates worker Git worktrees, records assignments in **Beads/Dolt**, launches external coding runtimes in tmux, and provides messaging and lifecycle tools. Workers are called **polecats**. Optional AI roles coordinate tasks, monitor workers, or process the merge queue.\n\nFor this trial, the flow was:\n\n```text\nCreate a tracked issue\n  → gt sling assigns it and creates a worker worktree\n  → Claude implements the website\n  → the worker records completion\n  → a human-facing browser check verifies the result\n```\n\nThat distinction matters: durable task state is not the same thing as durable model memory. And a separate worktree or tmux session is not a security sandbox.\n\nWe deliberately ran a narrow trial: **one task, one executing worker, no automatic infrastructure boot, and no merge queue.** There were failed worker-start attempts before the successful run, but this was not a multi-worker coordination benchmark.\n\n## The website we asked for\n\nWasteland Wire had to be a working application, not just a visual mockup:\n\n- Real RSS aggregation for Mad Max/post-apocalyptic entertainment, climate/environment, and science/resilience.\n- Publisher names, publication dates, source links, and short plain-text excerpts.\n- Category filters, search, newest/oldest sorting, and manual refresh.\n- Honest loading, empty, and upstream-error states.\n- Partial failures that do not erase headlines from working sources.\n- Original artwork and an explicit unofficial-fan disclaimer.\n- No paid news API or API keys.\n\nThe implementation uses a plain Node HTTP server, `rss-parser`, and vanilla HTML/CSS/JavaScript. Fonts are self-hosted. There is no frontend build pipeline.\n\nScience and climate reporting are labelled as real-world news, not falsely presented as proof that an apocalypse is imminent.\n\n## Actually driving Gas Town: the commands and prompts\n\nHere is the operational story missing from a simple architecture diagram. Commands in this section were executed inside the container unless prefixed with `docker`. Output excerpts are abridged; task and worker names are from the actual run.\n\n### From “build a fan site” to a durable assignment\n\nWe did **not** ask a Mayor to plan the project. The human-facing assistant wrote the product brief and explicitly dispatched one worker. That brief lived at `/gt/website-brief.md`, was initially committed as the project's README, and is now preserved in [docs/BRIEF.md](https://github.com/bdmorin/wasteland-wire/blob/main/docs/BRIEF.md).\n\nIts opening instructions were:\n\n> Build and run an unofficial Mad Max fan website that aggregates real post-apocalyptic-themed news. This is a real working website, not a mockup. Theme: cinematic desert-industrial editorial, sand/charcoal/rust, oversized condensed headlines, strong typography, responsive layout, readable contrast. No copyrighted movie stills or claims of official affiliation. Original CSS/SVG artwork is preferred.\n\nThe brief supplied the functional requirements, verification criteria, one already-checked feed, and concrete boundaries: no external repository writes, deployment, GitHub PRs, additional agents, or merge to main. It also told the worker not to fabricate news and to verify actual feed entries before choosing sources.\n\nWe gave the project a disposable local bare origin, then registered it:\n\n```bash\ngit init --initial-branch=main /gt/trial-source\ncp /gt/website-brief.md /gt/trial-source/README.md\ngit -C /gt/trial-source add README.md\ngit -C /gt/trial-source commit -m \"Define Wasteland Wire website requirements\"\ngit clone --bare /gt/trial-source /gt/trial-origin.git\n\ngt rig add wasteland file:///gt/trial-origin.git \\\n  --local-repo /gt/trial-source --prefix ww --branch main\n```\n\nGas Town created a shared bare repository, a mayor clone, a refinery worktree, a Beads directory, and agent bookkeeping. Creating those directories did not mean those AI roles were running.\n\nThe actual task creation command, run from the rig root, was:\n\n```bash\nbd create --json \\\n  --title 'Build and run Wasteland Wire Mad Max fan news website' \\\n  --type task --priority 2 \\\n  --description 'Implement all requirements in README.md and /gt/website-brief.md. Real RSS aggregation, polished responsive UI, filters/search/refresh, source attribution and honest failure states. Use Node.js. Run and verify the server; preserve work on a feature branch, no merging or deployment. Do not launch additional agents.'\n```\n\nIt returned **`ww-bx2`**. That ID—not a conversation floating in a terminal—became the assignment we could inspect, reassign, and eventually close.\n\n### Slinging the task: more than launching Claude\n\n```bash\ngt sling ww-bx2 wasteland --agent claude \\\n  --no-boot --no-convoy --no-merge --dry-run\n\ngt sling ww-bx2 wasteland --agent claude \\\n  --no-boot --no-convoy --no-merge\n```\n\nThe dry-run previewed the orchestration: instantiate `mol-polecat-work`, bond it to `ww-bx2`, update the issue to `hooked` with a worker assignee, and inject a startup prompt.\n\nThe real dispatch created worker **rust**, allocated its worktree, instantiated workflow **`ww-wisp-cad`**, attached the issue, and enabled no-merge mode. In other words, the coding session started inside an already assigned project/workflow context.\n\nThe flags mattered:\n\n- `--no-boot`: do not wake the rig's Witness/Refinery infrastructure.\n- `--no-convoy`: do not create an automatic work-tracking convoy for this single task.\n- `--no-merge`: preserve the feature branch for review instead of submitting it to the merge queue.\n\nNone is a security sandbox or an enforced spending cap.\n\n### What was the priming prompt?\n\nThe observed interactive Claude launch included Gas Town's role settings and an initial message whose actionable text was:\n\n```text\nRun `gt prime --hook` and begin work on your hook.\n```\n\nIts generated banner identified the worker and rig. We are not reproducing the entire generated role document here; the key is that **the startup message did not contain the whole website specification**. It directed the agent to recover its role, attached assignment, and workflow from Gas Town.\n\nThe successful headless worker **chrome** actually executed these commands, as recorded in its transcript:\n\n```bash\ngt prime --hook 2>&1 | tail -150\ngt prime --hook 2>&1 | grep -n \"Step\\|ww-bx2\" | head -40\nbd show ww-bx2\n```\n\nThose output-trimming pipelines were the worker's choices, not prerequisites for priming. The core operation was `gt prime --hook`.\n\nThe prime output contained the hooked issue, attached `mol-polecat-work` formula, and eight workflow steps:\n\n1. Load context and verify assignment.\n2. Set up working branch.\n3. Implement the solution.\n4. Commit all implementation changes.\n5. Self-review changes.\n6. Build and sanity check.\n7. Pre-merge rebase verification.\n8. Submit work and self-clean.\n\nThe output also directed the worker to inspect `bd show ww-bx2`, explained how to preserve findings in issue notes, and specified the eventual `gt done` handoff. Build/test gate variables were empty for this newly created rig; we had not configured a pre-existing project test command. The worker added and ran the site's own checks rather than inheriting a configured gate suite.\n\nOne revealing rough edge: the generic formula text described submitting to the merge queue even though the actual assignment had `no_merge: true`. The effective no-merge behavior was confirmed by the completion state, not by assuming every paragraph of the generic prompt reflected that override.\n\n### Watching and recovering the worker\n\nWe observed the actual session rather than trusting a successful dispatch:\n\n```bash\ngt peek wasteland/rust 70\ngt session status wasteland/rust\n```\n\nThat is how we discovered Claude was in onboarding instead of building the site. We attempted to advance it using the supported messaging interface:\n\n```bash\ngt nudge wasteland/rust '' --mode immediate\n```\n\nThis was an onboarding workaround attempt, not an implementation instruction. It did not solve interactive startup. We stopped/restarted sessions while investigating and ultimately registered the headless runtime described below.\n\nRe-dispatch retained the **same task ID**:\n\n```bash\ngt sling ww-bx2 wasteland --agent claude-build \\\n  --force --no-boot --no-convoy --no-merge\n```\n\nThe output made the state transition explicit:\n\n```text\nCreated polecat: chrome\nBead already hooked to wasteland/polecats/rust, forcing reassignment...\nSent LIFECYCLE:Shutdown to wasteland/witness for rust\nBurning 1 stale molecule(s) from previous assignment: ww-wisp-cad\nFormula wisp created: ww-wisp-p8v\nFormula bonded to ww-bx2\nWork attached to hook (status=hooked)\nNo-merge mode enabled (work stays on feature branch)\n```\n\nSending that lifecycle message did not mean a Witness was running to act on it; we supervised the trial ourselves. Once chrome was executing, `gt peek wasteland/chrome` exposed its streamed tool activity. This is the useful orchestration distinction: assignment and workflow history survived a failed runtime launch and explicit reassignment.\n\n### Completion had its own recovery step\n\nThe worker rebased onto `origin/main`, ran `npm run check`, and invoked:\n\n```bash\ngt done --pre-verified --target main\n```\n\nThe first attempt refused completion because it detected uncommitted files. Its attempted auto-commit failed. The worker inspected Git state, added Gas Town-only workspace paths to `.gitignore` (`.beads/`, `.claude/`, `.runtime/`, and root `CLAUDE.md`), committed that exclusion, and retried the same completion command.\n\nThis final remediation was absent from the initial account: **the implementation was done, but runtime scaffolding still interfered with the clean-worktree completion check.** Ignoring that scaffolding kept it out of the public website export.\n\nWe checked the durable result:\n\n```bash\nbd show ww-bx2\ngt polecat status wasteland/chrome\n```\n\nThe issue was **CLOSED**, with close reason **“No-merge work completed; merge queue skipped.”** Chrome was **done**, its session stopped, and its branch remained `polecat/chrome/ww-bx2+murp42sq`.\n\nThat is the end-to-end Gas Town story for this run: brief → bead → formula-backed assignment → primed worker → explicit recovery/reassignment → committed implementation → recorded no-merge completion. The remaining setup details below explain why getting to that path took repairs.\n\n## The setup: where the friction was\n\n### 1. Install the tools—but distinguish release from checkout\n\nThe initial machine had Git, Go, tmux, SQLite, Claude, Homebrew, and Docker tooling, but no `gt`, `bd`, or `dolt` on PATH.\n\n```bash\nbrew install gastown\n```\n\nHomebrew installed Gas Town **1.1.0**, Beads **1.3.1**, and Dolt **2.4.0**. Version and help commands worked.\n\nHowever, the inspected checkout declared **1.2.1**. Installing a packaged release is not testing that source tree. We built the repository's Docker image to exercise the checkout instead.\n\nThe container build took roughly **399 seconds** on this run. Because Git metadata was excluded from the image build context, the resulting CLI reported a `dev` version label. That label alone does not identify a release.\n\n### 2. Start the database explicitly\n\nThe container had an initialized headquarters, but `gt dolt status` reported that Dolt was not running.\n\n```bash\ngt dolt start\n```\n\nThat started the isolated SQL server. We did not establish why it was stopped after bootstrap; an observed symptom is not a root-cause diagnosis.\n\n### 3. Respect the schema-migration guard\n\nCreating the website rig exposed a Beads compatibility issue: **17 pending migrations, from schema v49 to v66**. Automatic migration was refused because the database was served through a shared Dolt server.\n\nThat refusal protects other clients: promoting the schema can lock out older binaries. In our case, the server was newly created and isolated, so an explicit migration was appropriate.\n\n```bash\nbd migrate schema\n```\n\nThe command's working directory mattered. An attempt from the mayor clone reported no Beads database; running from the rig root verified schema v66. Task creation then succeeded.\n\nThis was a useful reminder: **a working `bd version` command says nothing about whether `bd` can write to your actual database.**\n\n### 4. Account for bind-mount ownership\n\nGit rejected the HQ bind mount as having dubious ownership. We added the exact `/gt` directory to Git's safe-directory list inside the disposable container account.\n\nWe did not disable that protection globally on the host.\n\n### 5. Authentication does not cross the container boundary\n\nClaude was authenticated on the host, but not inside the container. We did not copy host credentials.\n\nThe interactive login had to happen in a real terminal:\n\n```bash\ndocker exec -it gastown-sandbox claude auth login\n```\n\nAn automation-side attempt using `-it` failed because stdin was not a terminal. A non-TTY attempt produced an OAuth code prompt but was not suitable for unattended completion.\n\nAfter the user's login, `claude auth status` reported success.\n\n### 6. Authenticated is not the same as ready\n\nThe first `gt sling` created the worker and hooked the issue correctly. But the actual Claude session showed its first-run theme selector, then a login-selection screen—not task execution.\n\nAdvancing the wizard through Gas Town's messaging interface did not resolve the problem. We also attempted an onboarding-config repair. That uncovered another operational trap: **`docker cp` can leave a configuration file owned by the wrong user.** The agent encountered `EACCES` until ownership was corrected.\n\nAn orphaned login process from an earlier attempt was also found and stopped. We did not prove it caused the configuration rewrites observed during these attempts.\n\nEven after the configuration was readable, interactive worker startup still failed with `startup blocked: no tmux server running`. The exact cause of the interactive exit remained unresolved.\n\n### 7. Prove inference separately, then use a headless runtime\n\nBefore continuing, we tested authenticated Claude inference directly from the assigned worktree:\n\n```bash\nclaude -p 'Reply with READY only.' --dangerously-skip-permissions\n```\n\nIt returned `READY`. The same smoke check worked with Gas Town's settings file.\n\nWe then registered a headless runtime through Gas Town:\n\n```bash\ngt config agent set claude-build \\\n  'claude -p --verbose --output-format stream-json --dangerously-skip-permissions'\n```\n\n**Security note:** automatic tool approval is appropriate here only because this was a disposable, isolated trial. It is not a recommendation for untrusted code or a credential-rich production environment.\n\nThe assignment was re-dispatched to a fresh worker with:\n\n```bash\ngt sling <task-id> wasteland --agent claude-build \\\n  --force --no-boot --no-convoy --no-merge\n```\n\nThis time the worker executed: streamed tool activity appeared, packages were installed, and website files were written.\n\nThe dispatch command still timed out after 90 seconds. The worker continued running. A mismatch between headless output and interactive readiness detection was our working interpretation—not a proved diagnosis.\n\n**The important distinction: headless execution worked; interactive startup was not fixed.**\n\n## The result: a real feed, not just a nice screenshot\n\nThe worker completed the task, recorded it as closed, and preserved the result on a feature branch. The merge queue was skipped as requested.\n\nThe website uses 11 fixed HTTPS feeds, including Google News searches, The Guardian, ScienceDaily, Yale Environment 360, Inside Climate News, Ars Technica, and Phys.org. Candidate feeds that were empty, malformed, noisy, or unavailable were rejected rather than silently replaced with fake data.\n\nThe server bounds feed fetches with a timeout and byte limit. Feed text is rendered as plain text; unsafe links are dropped. A failed source can retain its previous headlines, explicitly marked as cached copies.\n\nWe exercised the actual failure path during verification:\n\n1. The first host fetch succeeded for **10 of 11 sources**, yielding **139 headlines**. Phys.org timed out.\n2. The page named the failed source and kept the successful feeds visible.\n3. Manual refresh subsequently succeeded for **all 11 sources**, yielding **154 headlines**.\n\nThose counts are observations from this run, not promises about future upstream availability.\n\n## Verification beyond the worker's report\n\nThe container lacked a library needed for a real headless browser. The worker exercised frontend behavior with jsdom and reported that visual verification still needed to happen.\n\nWe then ran the website from the bind-mounted worktree on the host and checked it in real Chromium:\n\n- Desktop layout at 1440px and mobile layout at 390px.\n- Category filtering and a Furiosa search returning nine headlines.\n- No-match empty state and clear-filter recovery.\n- Oldest-first sorting with ascending publication dates.\n- Refresh controls disabled while fetching.\n- Real source links and API responses.\n- No browser errors observed during the exercised flows.\n- Mobile document width matched the viewport, with no horizontal overflow observed.\n\n`npm run check` also passed syntax checks and **all 30 deterministic tests**, covering parsing, safe URLs, bounded fetches, partial failure, stale caches, filtering, and HTTP behavior.\n\nThe point wasn't to accept “the agent said it worked.” We ran the resulting application and observed its behavior.\n\n## What this trial did—and did not—prove\n\nIt proved a useful single-worker path: Gas Town tracked the task, allocated the worktree, launched an external runtime, and recorded completion. The coding agent produced a working, good-looking website.\n\nIt did **not** test multi-worker coordination, unattended recovery, or automatic merge processing. For one task, much of the orchestration benefit is modest compared with running Claude directly. Public hobby hosting was added afterward, as described below; that is not evidence of production hardening.\n\nThe feed also has editorial limitations. Some Guardian stories are visibly dated archive entries. Keyword searches occasionally match articles using “Mad Max” metaphorically rather than covering the films. The cache is in memory and resets on restart.\n\n## Where to host it simply\n\nThe app needs a Node backend for RSS aggregation, so **GitHub Pages alone is not a fit** without changing the architecture.\n\nThe website source is public at [bdmorin/wasteland-wire](https://github.com/bdmorin/wasteland-wire), with a free-tier `render.yaml` Blueprint. The user authorized Render and deployed that Blueprint; the dashboard reported the service live. The site is now available at **[wasteland-wire.onrender.com](https://wasteland-wire.onrender.com)**.\n\nWe checked the public deployment in real Chromium: the page and API returned 139 headlines from 10 of 11 feeds, correctly reporting a Phys.org connection failure while keeping other sources visible. Category filtering and a Furiosa search worked (eight matches on that public fetch); the mobile document width matched its 390px viewport, and no browser errors were observed in those flows. Feed/search counts vary with upstream responses, so the earlier local nine-match observation is not an invariant.\n\nThe [Render web service](https://render.com/docs/deploy-node-express-app) uses:\n\n| Setting | Value |\n| --- | --- |\n| Build command | `npm ci` |\n| Start command | `npm start` |\n| Environment | `HOST=0.0.0.0` |\n| Port | Use Render's supplied `PORT` |\n| Health check | `/api/health` |\n\nThe existing server already supports `HOST` and `PORT`; Gas Town, Dolt, and Claude are not needed to host the finished site.\n\nRender's [free service](https://render.com/docs/free) is suitable for a hobby preview, but it sleeps after 15 minutes without traffic and can take about a minute to wake. Its usage limits apply, and restarting loses the in-memory news cache. [Deploy your own copy](https://render.com/deploy?repo=https://github.com/bdmorin/wasteland-wire).\n\n## The takeaway\n\n**Install status, database compatibility, authentication, runtime readiness, and application correctness are separate checks.** Passing one does not imply the others.\n\nThe best part of this experiment was the final website. The most useful part was keeping an honest remediation trail: what failed, what changed, what was observed, and what remained unresolved.\n\nWe used Gas Town to build a wasteland. The scenery was excellent. The road there needed a few repairs.\n", "url": "https://wpnews.pro/news/we-used-gas-town-to-build-a-wasteland-an-honest-remediation-gastown-log", "canonical_source": "https://gist.github.com/bdmorin/b14ce7b054046cbc0153666e02abb196", "published_at": "2026-10-03 01:56:28+00:00", "updated_at": "2026-10-03 02:36:21.315438+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "generative-ai"], "entities": ["Gas Town", "Claude", "Wasteland Wire", "Beads", "Dolt", "Render", "GitHub", "tmux"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/we-used-gas-town-to-build-a-wasteland-an-honest-remediation-gastown-log", "markdown": "https://wpnews.pro/news/we-used-gas-town-to-build-a-wasteland-an-honest-remediation-gastown-log.md", "text": "https://wpnews.pro/news/we-used-gas-town-to-build-a-wasteland-an-honest-remediation-gastown-log.txt", "jsonld": "https://wpnews.pro/news/we-used-gas-town-to-build-a-wasteland-an-honest-remediation-gastown-log.jsonld"}}