5.7 KiB
Balda Setup & Status
Last updated: 2026-06-11
Runtime
| Item | Value |
|---|---|
| Binary | ~/Developer/balda/balda (built from source) |
| Process | Managed by launchd (com.normahq.balda) |
| Log | /private/tmp/balda.log |
| Config | ~/Developer/balda/.config/balda/config.yaml |
| State dir | ~/Developer/balda/.config/balda/ |
| Sessions | SQLite persistence |
| Provider | deepseek (openai type, model: deepseek-chat, via https://api.deepseek.com/v1) |
Balda is managed by launchd. Full restart (reloads plist env vars):
launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/com.normahq.balda.plist
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.normahq.balda.plist
Quick restart (does NOT reload plist env vars — use bootout/bootstrap for env changes):
launchctl kickstart -k gui/$(id -u)/com.normahq.balda
Zulip Webhook
| Item | Value |
|---|---|
| Status | ENABLED |
| Listen address | 0.0.0.0:8091 |
| Path | /zulip/webhook |
| Webhook bot URL | http://host.docker.internal:8091/zulip/webhook |
Port was changed from 8090 → 8091 on 2026-06-07 (see Port Conflict below).
Architecture
Balda uses the outgoing webhook mechanism — Zulip pushes events to Balda's HTTP endpoint. This differs from Eagle/Kit which use polling.
- Zulip runs in Docker (container:
zulip-zulip-1), exposed on127.0.0.1:8000 - Balda runs natively on Mac (not in Docker)
- Colima maps
host.docker.internal:PORT → 127.0.0.1:PORTon the Mac host, so the bot URL useshost.docker.internalto reach Balda from inside Docker
Soul / Prompt
The global_instruction field in config.yaml defines Balda's persona (Валера).
The canonical source is soul.md in this folder — sync manually to config after edits,
then restart Balda.
Provider-level system_instructions are documented in workspace-context.md.
Bug Fixed 2026-06-07 — Context Canceled on First DB Operation
File: zulip_handler.go, line 236
Root cause: The message-processing goroutine was launched with the HTTP request context:
// Before (broken)
go h.processMessage(r.Context(), payload)
When the handler returns HTTP 200, Go cancels r.Context(). The goroutine then
hits its first database operation and fails with context canceled.
Fix:
// After (fixed)
go h.processMessage(context.WithoutCancel(r.Context()), payload)
context.WithoutCancel creates a copy of the parent context that is never
canceled when the parent is — so the goroutine lives past the HTTP handler return.
Port Conflict 2026-06-07 — Moved 8090 → 8091
Root cause: Colima (Docker runtime) maps host.docker.internal:PORT → 127.0.0.1:PORT on the Mac host. openclaw/claude-proxy was occupying
127.0.0.1:8090, intercepting Balda's webhook traffic before it reached Balda.
Solution: Changed Balda's listen port from 8090 to 8091 in config.yaml.
Resolution
Webhook bot URL updated in Zulip DB via docker exec psql on 2026-06-07:
http://host.docker.internal:8090/zulip/webhook → http://host.docker.internal:8091/zulip/webhook
Smokescreen SSRF Proxy — Allow Private Ranges (Fixed 2026-06-11)
Symptom: Zulip outgoing webhook to Balda fails with HTTP 407.
Zulip logs: client: OutgoingWebhookResponse + "Failure! Third party responded with 407".
Root cause: Zulip runs Smokescreen (SSRF proxy) for all outgoing HTTP.
host.docker.internal resolves to a private IP (192.168.5.x), which Smokescreen
blocks by default. A docker restart does NOT recreate containers, so env vars
added to docker-compose aren't picked up — must use docker compose up -d.
Fix: Added to ~/Developer/zulip/docker-compose.yml under the zulip service env:
PROXY_ALLOW_RANGES: "10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
Then recreated containers:
cd ~/Developer/zulip
docker compose up -d
Smokescreen now starts with --allow-range flags for all RFC1918 ranges.
Verify with: docker exec zulip-zulip-1 ps aux | grep smokescreen
Note: SETTING_ALLOW_BOTS_TO_MAKE_REQUESTS_TO_PRIVATE_ADDRESSES: "True" was already
set but doesn't affect Smokescreen's --allow-range flags — it controls a different
check. PROXY_ALLOW_RANGES is the correct env var.
DeepSeek Provider via norma-local Fork
Balda's openai provider type in norma hardcoded api.openai.com. To support
DeepSeek (or any OpenAI-compatible API), the norma source was forked locally:
- Fork path:
~/Developer/norma-local - Patched files:
pkg/runtime/hostedagent/openai.go— addedopenAIBaseURL()readingOPENAI_BASE_URLenv varpkg/runtime/agentfactory/agentfactory.go— removed MCP restriction foropenaitype
- Env var
OPENAI_BASE_URL=https://api.deepseek.com/v1is set in the launchd plist - go.mod
replacedirective (normahq/norma v0.0.6 → ../norma-local) is local only (not committed to the PR branch — needs a separate norma upstream PR or fork)
To rebuild after patching norma:
cd ~/Developer/balda
go build -o balda ./cmd/balda/
CLAUDECODE Environment Bug (Fixed 2026-06-07)
When launched from an Eagle terminal session, Balda inherited CLAUDECODE=1,
which caused the Claude Code CLI to refuse to start (it detected a nested invocation).
Fix: Use launchctl bootout + bootstrap to start Balda clean — launchd does
not inherit parent shell env vars.
Config Snippet (key sections)
balda:
global_instruction: |
<contents of soul.md>
runtime:
providers:
deepseek:
type: openai
openai:
api_key: "<DeepSeek API key>"
model: deepseek-chat
zulip:
webhook:
enabled: true
listen: "0.0.0.0:8091"
path: /zulip/webhook
allowed_owners:
- amartemyanov@duckduckgo.com