WhatsApp: QR won't scan
Symptom: rousseau whatsapp prints a QR that the phone app rejects, or the pairing dialog reads "This device is not paired with WhatsApp."
Fixes:
- Rebuild the container. If you are running an older image,
whatsmeowmay have shipped a protocol update. Rebuild:podman build -t rousseau-agent:local -f docker/Dockerfile . systemctl --user restart rousseau-agent.service
- Delete
whatsapp.db. A partially-completed pairing leaves the DB in a state whatsmeow cannot reuse. Delete it and re-pair:rm ~/.local/share/rousseau/whatsapp.db
- Check clock skew. WhatsApp's handshake is time-sensitive. If the container's clock is off by more than 30 seconds, pairing fails silently.
timedatectl status
WhatsApp reconnect loop
Symptom: logs show repeated whatsapp.connected followed by whatsapp.disconnected every few seconds.
Fixes:
- Clock skew. Same fix as above.
- Allowlist misconfigured. Every inbound message is dropped as unauthorised; some servers close the socket after too many silent drops. Add the correct JIDs with
--allow. - Meta-side ban. If the WhatsApp mobile app shows "This device has been logged out," Meta has invalidated the pairing. Re-pair from a fresh QR. If it happens repeatedly on the same number, stop using that number.
cosign verify-blob fails
Symptom:
Error: no matching signatures
Fixes:
- Wrong certificate-identity regex. The regex must match the GitHub repository that signed the release. For rousseau-agent releases the correct value is:
Do not use--certificate-identity-regexp 'sebastienrousseau/rousseau-agent'.*— that would accept a cosign signature from any repository. - Wrong OIDC issuer. GitHub Actions cosign signatures issue from
https://token.actions.githubusercontent.com. Other CI providers (GitLab, Buildkite) issue from different URLs. - Wrong signature file. Check that
<version>_checksums.txt.sigcorresponds to the_checksums.txtyou are verifying (not a stale copy from a different release). - Sigstore trust root changed. Refresh with
cosign initialize; the trust root is updated on a slow rotation.
Container fails to bind mount
Symptom: podman play kube or systemctl --user start rousseau-agent.service fails with permission denied on a bind mount.
Fixes:
- SELinux label. Every volume line must end with
:Z(or:zfor shared) so Podman applies the correct SELinux label:Volume=%h/.local/share/rousseau:/home/rousseau/.local/share/rousseau:rw,Z:Z(uppercase) is the private label — appropriate for single-container mounts.:z(lowercase) shares the label across containers. keep-idmapping. WithoutUserNS=keep-id, container UID 1000 is remapped into the host's subuid range and cannot write to host-owned files. Ensure the Quadlet has:UserNS=keep-id- Missing directory. Podman does not auto-create bind-mount sources. Create the directory first:
mkdir -p ~/.local/share/rousseau
Cron job not firing
Symptom: rousseau cron list shows the job, but nothing happens at the scheduled time.
Fixes:
- Check status.
rousseau statusreports scheduler activity. If the scheduler is not running, the daemon that hosts it is not running. - Timezone. Schedules use the server's local timezone. Confirm with
timedatectl. SetTZ=UTCin the Quadlet if you want deterministic scheduling regardless of host locale. - PollInterval delay. New jobs go live within
PollInterval(default 60s). Wait one minute. - Delivery failure. The job fired but delivery failed. Check logs for
cron.delivery_failed; the target format is transport-specific (see /cron/).
Approval policy rejecting everything
Symptom: every tool call is denied with "denied by pattern policy" and the model refuses to make progress.
Fixes:
- Missing allow rule. In
patternmode withdefault: deny, every tool call needs a matching allow rule. Add one for the tools you want to permit:agent: approver: mode: pattern default: deny allow: - {tool: read, match: ".*"} - {tool: grep, match: ".*"} - {tool: edit, match: "^./workspace/.*"}
- Deny beats allow. A
denyrule always wins over anallowrule for the same tool. Review your deny list for accidental over-matches. - Raise the default. For attended sessions,
default: allowwith tightened deny rules is often more workable:agent: approver: mode: pattern default: allow deny: - {tool: bash, match: "rm -rf|sudo"}
Provider returns 401
Symptom: agent errors with provider: unauthorized.
Fixes:
- Wrong API key. For the Anthropic direct provider, verify
ANTHROPIC_API_KEYis exported or set in~/.config/rousseau/config.yaml. - Wrong credential chain. For Bedrock, run
aws sts get-caller-identityfrom the container to confirm which principal the SDK resolves. - Vertex service account. For the Vertex provider, confirm the file at
vertex.credentials_fileis readable inside the container and grantsroles/aiplatform.user.
Provider returns 429
Symptom: agent errors with provider: rate limited.
Fixes:
- Lower
max_tokens. Shorter completions clear the rate window faster. - Enable compression. Long transcripts increase input-token pressure;
agent.compression.enabled: truecollapses old messages. - Wait it out. rousseau does not retry inside
Complete; the caller (chat transport, cron scheduler, orrousseau chat) decides whether and how to retry.
rousseau chat shows only a blank TUI
Symptom: the Bubble Tea TUI opens but no cursor, no viewport.
Fixes:
- TERM environment. rousseau requires an ANSI-capable terminal. Set
TERM=xterm-256color(or similar). - Wrapped stdin. Running under
nohupor a pipe strips the terminal. Run interactively.
Slack: invalid_auth on start
Symptom: slack.starting immediately followed by invalid_auth.
Fixes:
- Wrong token mixed up. Rousseau needs both
xapp-…(app-level,--app-token) andxoxb-…(bot,--bot-token). Passing an app token where a bot token is expected produces this error. - App not installed. After creating scopes, click Install to Workspace in the Slack app config. Tokens are only valid post-install.
- Token rotated. Slack tokens can be manually rotated by an admin. If you rotated one, all daemons using it must be restarted with the new value.
Slack: bot replies to its own messages (loop)
Symptom: rousseau's outbound message triggers an inbound event that the daemon responds to, causing runaway replies.
Fixes:
- Set
bot_user_id. The--bot-user-idflag (orslack.bot_user_idin config) tells the daemon to ignore messages sent by that user ID. Retrieve it withcurl -H "Authorization: Bearer xoxb-..." https://slack.com/api/auth.test. - Verify the event filter. The transport ignores
bot_messagesubtypes by default, but a poorly-configured Slack app can bypass this.
Discord: message text arrives empty
Symptom: discord.incoming from=... body= — messages come through but with no content.
Fixes:
- Message Content Intent disabled. In the Discord Developer Portal under Bot > Privileged Gateway Intents, toggle Message Content Intent. Without it, Discord strips message text from Gateway events.
- Missing scopes. The invite URL must have granted the bot
Read Message HistoryandSend Messagesfor the channel/DM you are using.
Discord: disallowed intents
Symptom: startup errors with Discord returned 4014 disallowed intents.
Fixes:
- Privileged intents. Enable Message Content Intent (see above). Even if you never ask for it, Discord returns 4014 if you request it without approval.
- Verification. Bots in 100+ servers must be verified by Discord to use privileged intents. Follow the developer portal walkthrough.
Telegram: unauthorized
Symptom: telegram.starting followed by getUpdates: 401.
Fixes:
- Wrong token. BotFather returns the token once — do not include the trailing period. The token has the shape
<bot_id>:<secret>. - Token revoked.
/revokein BotFather invalidates the current token; get a fresh one.
Email: dial tcp: i/o timeout
Symptom: the IMAP or SMTP connect never completes.
Fixes:
- Wrong port. IMAP is
993(implicit TLS). SMTP submission is587(STARTTLS) or465(implicit TLS). Rousseau uses implicit TLS on both — STARTTLS-only servers are not yet supported. See Transports: Email for migration. - Egress blocked. Corporate firewalls often block outbound SMTP. Test with
openssl s_client -connect smtp.example.com:465from the container. - Provider requires app password. Gmail, Fastmail, and similar require an app password (not your account password) when 2FA is on. Generate one from the provider's security settings.
Vertex: permission denied on resource
Symptom: vertex: HTTP 403 permission denied on resource projects/.../models/claude-sonnet-4-6@…:rawPredict.
Fixes:
- Missing role. Grant
roles/aiplatform.userto the service account or user calling the API. IAM changes take up to a minute to propagate. - Wrong project. The
projectin config must match the project that owns the quota. If billing is on a different project, use quota-project viagcloud auth application-default set-quota-project. - Region mismatch. The model must be available in the requested region — Vertex Model Garden lists this.
Bedrock: You don't have access to the model
Symptom: AccessDeniedException: You don't have access to the model with the specified model ID.
Fixes:
- Model access not requested. Bedrock requires explicit model-access request via the console (Foundation models > Model access). Even with IAM permitting
InvokeModel, this step is required. - Wrong region. Model availability is regional. Check the Bedrock console.
- Cross-account misconfiguration. If using AssumeRole, verify the target role's policy allows
bedrock:InvokeModelon the exact model ARN.
Ollama: context deadline exceeded
Symptom: rousseau times out while Ollama is still generating.
Fixes:
- CPU inference is slow. A 70B model on a laptop CPU can take minutes per turn. Use a smaller model (
llama3.1:8b) or a GPU host. - Timeout inheritance. rousseau uses the SDK default HTTP timeout. If you wrap the provider yourself, extend the timeout to at least 120 s.
Voice notes: transcriber not configured
Symptom: whatsapp.audio_ignored reason=transcriber_not_configured.
Fixes:
- Whisper disabled. Set
whatsapp.voice.enabled: truein config and ensure thewhisperbinary is onPATH(or setwhatsapp.voice.binaryto an absolute path). - Model file missing. Set
whatsapp.voice.model_pathto an explicit.binfile. Whisper.cpp models are downloaded manually — the config points at where they live.
Session store: database is locked
Symptom: WAL writer blocks; requests time out.
Fixes:
- Two daemons, one DB. SQLite with WAL supports concurrent readers but only one writer. If you run two rousseau processes against the same
state.path, one will block. Use different state paths. busy_timeouttoo low. The DSN setsbusy_timeout=15000. Under sustained contention, raise it — but investigate the root cause first.- Stale WAL file. A crashed writer can leave
sessions.db-wallocked. Stop everything, deletesessions.db-walandsessions.db-shm, restart.
MCP: Claude Desktop does not see rousseau tools
Symptom: rousseau launched via command: "rousseau" in claude_desktop_config.json but no tools appear.
Fixes:
- Config not saved. Claude Desktop hot-reloads on save; if you edited the file in a running instance, restart it.
commandnot on PATH. Claude Desktop launches subprocesses from its own environment;/usr/local/bin/rousseaumay not be visible. Use an absolute path.- stderr noise. rousseau writes structured logs to stderr; a very chatty logger can overwhelm the host. Set
log.level: warnwhen running MCP against a strict host.
Skills: skill loader: parse: yaml: line X
Symptom: rousseau errors on start with a YAML parse error.
Fixes:
- Malformed frontmatter. Skills use
----delimited YAML frontmatter. Ensure both fences are present and there is no tab indentation. - Unquoted colons. A colon inside a value (
description: this: that) is parsed as a nested map. Quote the value:description: "this: that".
rousseau doctor reports warn
Symptom: doctor completes but with amber rows.
Fixes:
- Read the reason. Every warn row includes a reason. Common ones:
whatsapp.paired=false(never linked),state.wal_size=large(checkpoint overdue),provider.claudecli.model=unset(using claude's default). - Warns are not failures. The daemon will start; the row is signalling something worth reviewing.
Kubernetes: pod stuck in CrashLoopBackOff
Symptom: the deployment never reaches Ready.
Fixes:
- Read the logs.
kubectl logs -p <pod>shows the previous container's stderr. Nine times out of ten it is a config or credential error. - Missing state volume. Without a PVC for
~/.local/share/rousseau, pairing does not survive restart and the daemon may loop trying to re-pair. - IRSA / Workload Identity misconfiguration. Verify the service account annotation matches an IAM role that has provider permissions.
kubectl execinto the pod and runaws sts get-caller-identity(Bedrock) orgcloud auth print-access-token(Vertex) to confirm.
nftables ruleset blocks provider egress
Symptom: dial tcp: i/o timeout on the first provider call after applying an egress ruleset.
Fixes:
- CIDR rotated. Provider IP ranges change. Use DNS-based egress via an ipset that refreshes on a cron, or use an egress proxy that resolves at connect time.
- DNS blocked. Egress ruleset must allow UDP/53 (or TCP/53) to your DNS resolver.
Structured logs missing fields
Symptom: whatsapp.incoming shows up with from and no other attributes.
Fixes:
- Log level too high. Some fields are only emitted at
debug. Setlog.level: debugin config. - JSON parser eating fields. Piping through a filter that strips unknown fields can drop
elapsed,bytes, etc. Verify against raw stdout.
Related pages
- Getting Started: First Transport — end-to-end walkthrough.
- Providers — per-provider troubleshooting.
- Transports — per-transport troubleshooting.
- Configuration — the source of truth for every knob.
- Security — trust boundaries and audit trail.
Further reading
internal/cli/doctor.go— the doctor implementation.internal/state/sqlite/store.go— session-store DSN and WAL handling.internal/transport/router.go— inbound event routing and allowlist.- Slog attribute key reference — every
.info()/.warn()/.error()in the source tree.