Knowledge Base / FAQ
Lessons learned from building, deploying, and debugging Gasclaw. This document covers real issues encountered in production and their solutions.
Gastown Integration
Q: What is the "real" Gastown?
Gastown is a Go-based multi-agent orchestration system at github.com/steveyegge/gastown. It provides the gt CLI for managing agent workspaces, along with a companion tool bd (beads) for git-backed issue tracking.
The correct way to install Gastown in a Dockerfile:
RUN go install github.com/steveyegge/gastown/cmd/gt@latest
RUN go install github.com/steveyegge/beads/cmd/bd@latest
Do not use pip install gastown — that installs a different (incorrect) package.
Q: What gt subcommands does Gasclaw use?
| Command | Purpose |
|---|---|
gt install |
Initialize the Gastown workspace |
gt rig add <url> |
Register a project repository |
gt config agent set <name> <cmd> |
Register a named agent |
gt config default-agent <name> |
Set the default agent |
gt daemon start / gt daemon stop |
Manage the daemon process |
gt mayor start --agent <name> |
Start the mayor agent |
gt agents |
List running agents (not gt status --agents) |
Q: Why does gt rig add need cwd=gt_root?
The gt rig add command must be run from the Gastown installation directory (/workspace/gt). Without the correct cwd, it fails silently or creates the rig in the wrong location.
Kimi Proxy / Claude Code Backend
Q: How does Kimi work as the Claude Code backend?
Claude Code CLI reads ANTHROPIC_BASE_URL and ANTHROPIC_API_KEY from the environment. By setting:
...every claude invocation talks to Kimi K2.5 instead of Anthropic. This is handled automatically by build_claude_env() in kimigas/proxy.py.
Q: Why can't we use --dangerously-skip-permissions?
The --dangerously-skip-permissions flag is rejected when running as root (which is common in Docker containers). Instead, Gasclaw writes a Claude config file that pre-approves permissions:
{
"hasCompletedOnboarding": true,
"bypassPermissionsModeAccepted": true,
"customApiKeyResponses": {
"approved": ["<last-20-chars-of-api-key>"]
}
}
This is written by write_claude_config() in kimigas/proxy.py to an isolated config directory (~/.claude-kimigas). The CLAUDE_CONFIG_DIR env var points Claude CLI to this directory.
Q: How does key rotation work?
The KeyPool class in kimigas/key_pool.py implements LRU (Least Recently Used) rotation:
- Keys are sorted by last-use timestamp
get_key()returns the least-recently-used available key- When a key hits a 429 (rate limit), call
report_rate_limit(key)to quarantine it for 5 minutes - If ALL keys are rate-limited, the pool returns the key closest to cooldown expiry (graceful degradation)
- State is tracked by BLAKE2b hash — raw keys are never persisted to disk
Minimum recommendation: 2-3 keys for uninterrupted service.
Q: Why are Gastown and OpenClaw keys separate?
| Pool | Env Var | Purpose |
|---|---|---|
| Gastown | GASTOWN_KIMI_KEYS |
Agent workers (Mayor, Crew) |
| OpenClaw | OPENCLAW_KIMI_KEY |
Overseer bot (Telegram, monitoring) |
Separating pools ensures the overseer can always function even when all agent keys are rate-limited. Never put the same key in both pools.
OpenClaw & Telegram
Q: Why doesn't the bot respond to messages?
This is the most common issue. Check in order:
-
Is the gateway running?
-
Is
dmPolicyset to"open"? If set to"allowlist", only users inallowFromcan talk to the bot. For open access, setdmPolicy: "open"andallowFrom: ["*"]. -
Is
groupPolicyset to"open"? Valid values:"open","disabled","allowlist". The value"owner"does not exist and will be silently ignored. -
Is
requireMentiondisabled for groups? By default, OpenClaw requires @mention in groups. You must setchannels.telegram.groups: { "*": { "requireMention": false } }to make the bot reply to all group messages. TheackReactionScopeonly controls the acknowledgment emoji reaction — it does NOT control whether the bot actually replies. -
Is the bot an admin or is privacy mode disabled? Telegram bots have Privacy Mode enabled by default, which blocks them from seeing group messages unless @mentioned. Either make the bot a group admin, or disable privacy mode via BotFather (
/setprivacy-> Disable). After changing privacy mode, remove and re-add the bot to each group. -
Is
streamingoff? Some Telegram setups don't support streaming. Setstreaming: "off".
Q: What is the correct OpenClaw Telegram config?
{
"channels": {
"telegram": {
"enabled": true,
"botToken": "YOUR_TOKEN",
"dmPolicy": "open",
"allowFrom": ["*"],
"groupPolicy": "open",
"groups": { "*": { "requireMention": false } },
"streaming": "off"
}
},
"messages": {
"ackReactionScope": "all"
}
}
Key fields:
| Field | Value | Why |
|---|---|---|
dmPolicy |
"open" |
Accept DMs from everyone |
allowFrom |
["*"] |
Required when dmPolicy is "open" |
groupPolicy |
"open" |
Accept all group senders |
groups.*. requireMention |
false |
Reply to all group messages without @mention |
ackReactionScope |
"all" |
Show acknowledgment reaction on all messages |
streaming |
"off" |
Avoid Telegram streaming issues |
Important: ackReactionScope only controls the emoji reaction — it does NOT make the bot reply. The groups.*.requireMention: false setting is what actually disables the @mention requirement for group replies.
Q: What values does groupPolicy accept?
Only these three: "open", "disabled", "allowlist". The value "owner" was tried during debugging and does not work — it is silently ignored by OpenClaw.
Q: How do I add the bot to a group?
- Add the bot to the Telegram group
- Set
groupPolicy: "open"inopenclaw.json - Set
ackReactionScope: "all"so the bot responds to all messages, not just @mentions - Restart the gateway:
openclaw gateway stop && openclaw gateway start
Q: OpenClaw overwrites my config on restart!
The entrypoint.sh in the maintainer container writes openclaw.json on every startup (step 9). Manual openclaw config set changes will be overwritten. To make permanent changes:
- Edit
src/gasclaw/openclaw/installer.py(for the Python module) - Edit
maintainer/entrypoint.shstep 9 (for the Docker container) - Both must be in sync
Docker / Container Issues
Q: Why does the container crash loop?
The most common cause is a file permission error in entrypoint.sh. Since the script uses set -euo pipefail, any failing command kills the container.
Common triggers:
| Command | Failure | Fix |
|---|---|---|
cp /opt/agent-soul.md "$AGENT_WORKSPACE/SOUL.md" |
Permission denied (UID mismatch) | chown -R 1000:1000 /workspace/ |
dolt sql-server |
Port 3307 already in use | pkill -f "dolt sql-server" |
gt daemon run |
GT_HOME not writable | Check directory permissions |
Diagnosing crash loops:
Q: Why do file permissions fail in the container?
Docker volumes may be owned by a different UID than the container user. The maintainer user (UID 1000) needs to own /workspace/:
Q: How does Dolt get stopped reliably?
The old approach dolt sql-server --stop was unreliable in containers. Gasclaw now uses:
This ensures the Dolt process is terminated regardless of its state.
Bootstrap & Health
Q: What is the bootstrap sequence?
The 10-step bootstrap in bootstrap.py:
| Step | Action | What Happens |
|---|---|---|
| 1 | Setup Kimi proxy | Write kimi accounts, init KeyPool, set ANTHROPIC_BASE_URL, write Claude config |
| 2 | Install Gastown | gt install + gt rig add |
| 3 | Configure agent | gt config agent set kimi-claude claude + set as default |
| 4 | Start Dolt | Launch Dolt SQL server on configured port |
| 5 | Configure OpenClaw | Write ~/.openclaw/openclaw.json |
| 6 | Install skills | Copy skills to ~/.openclaw/skills/ |
| 7 | Run doctor | openclaw doctor --repair |
| 8 | Start daemon | gt daemon start |
| 9 | Start mayor | gt mayor start --agent kimi-claude |
| 10 | Notify | Send "Gasclaw is up" via Telegram |
If any step fails, all previously started services are rolled back automatically.
Q: What health checks are performed?
| Service | Method | Healthy When |
|---|---|---|
| Dolt | dolt sql -q "SELECT 1" |
Query returns successfully |
| Daemon | gt daemon status |
Process is running |
| Mayor | gt mayor status |
Process is running |
| OpenClaw | GET http://localhost:18789/health |
HTTP 200 |
| Activity | git log --since=<deadline> |
Commits exist within deadline |
Q: What does gt agents return vs gt status --agents?
Use gt agents (correct). The flag --agents on gt status does not exist and will error.
Testing
Q: How do I run the tests?
make test # 1021 unit tests (no API keys or services needed)
make test-all # Includes integration tests
make lint # Ruff linting
Q: Why do tests hang?
If bootstrap-related tests hang, it's usually because os.environ.update() is leaking real environment changes. Fix by patching:
@patch("gasclaw.bootstrap.build_claude_env", return_value={})
@patch("gasclaw.bootstrap.write_claude_config")
def test_bootstrap(mock_config, mock_env, ...):
...
Q: Should I modify tests to make them pass?
No. The project rule is: never modify a test to make it pass — fix the code. Tests define the contract.
Q: What mocking patterns are used?
- subprocess calls:
monkeypatch.setattr("subprocess.run", ...)or@patch("subprocess.run") - HTTP calls:
respxfor mockinghttpxrequests - File I/O:
tmp_pathfixture for isolated file operations - Environment:
monkeypatch.setenv()andmonkeypatch.delenv()
Configuration
Q: Where does OpenClaw config live?
~/.openclaw/openclaw.json — written by write_openclaw_config() in src/gasclaw/openclaw/installer.py.
Q: Where does the Claude config live?
~/.claude-kimigas/.claude.json — written by write_claude_config() in src/gasclaw/kimigas/proxy.py. The path is set via CLAUDE_CONFIG_DIR.
Q: Where are Kimi accounts stored?
~/.kimi-accounts/<n>/config.toml — one per key, written by setup_kimi_accounts() in gastown/installer.py.
Q: How do I add a new environment variable?
- Add field to
GasclawConfigdataclass inconfig.py - Add env var parsing in
load_config() - Add tests in
tests/unit/test_config.py - Add to the env var tables in docs
Git & Contributing
Q: What branch naming convention is used?
fix/— Bug fixesfeat/— New featurestest/— Test additionsdocs/— Documentationrefactor/— Code restructuring
Q: What's the commit message format?
<type>: <description>
Examples:
fix: integrate real Gastown CLI and correct subprocess commands
feat: add KeyPool LRU rotation for Kimi keys
docs: update architecture and troubleshooting guides
Q: What should every PR include?
- All 1021 tests passing (
make test) - Lint passing (
make lint) - New code has corresponding tests
- Commit message follows
<type>: <description> - PR description with summary and test plan