Troubleshooting — Infrastructure¶
Common issues and fixes across the Dashecorp agent fleet, CI/CD pipelines, and developer tooling.
Table of Contents¶
- Agent Offline / Not Responding
- GitHub App Token Issues
- Claude OAuth Token Expiry
- Discord Bot Issues
- CI / GitHub Actions Failures
- Docker Container Issues
- Git / Push Errors
- Markdown / YAML Lint Failures
- Escalation
Agent Offline / Not Responding¶
Pi-E not responding in Discord¶
Symptom: No reply in #pi-e after several minutes.
Diagnose:
ssh -i ~/.ssh/rpi-claude claude@100.107.48.17
systemctl status agent-runner
journalctl -u agent-runner -n 50
Fix:
If the service won't start, check logs:
Token expired? See Claude OAuth Token Expiry.
Volt-E not responding¶
Symptom: No reply in #volt-e; agent-runner process down.
Diagnose:
ssh -i ~/.ssh/vps-srv1099021 root@100.111.142.118
systemctl status agent-runner
journalctl -u agent-runner -n 50
Fix:
Review-E not responding¶
Symptom: No review posted on a dashecorp/* PR (rig-dispatched) or a Stig-Johnny/claude-3 PR (via request-review.yml).
Note: Review-E no longer runs on the Pi4-02 host (retired). It runs in the GCP k3s cluster (europe-north1-b) as a consumer of the Valkey assignments:review-e stream (KEDA-scaled), posting as the review-e-bot[bot] GitHub App.
Diagnose:
gcloud compute ssh invotek-k3s --zone=europe-north1-b --project=invotek-github-infra
kubectl get pods -n review-e
kubectl logs -n review-e review-e-rig-agent-runtime-0 --tail=200
# Check the assignment stream depth (queue should be ≤ 1 on a healthy rig)
kubectl -n rig-conductor exec valkey-primary-0 -- valkey-cli XLEN assignments:review-e
If the stream is backing up or pods are unhealthy, see the rig-dispatch sections below.
iBuild-E not responding¶
Symptom: No build triggered on agent-ready issue; #ibuild-e silent.
Diagnose:
ssh -i ~/.ssh/mac-executor claude@100.92.170.124
launchctl list | grep ibuild
# Check logs
cat ~/Library/Logs/ibuild-e/agent.log | tail -50
Fix:
rig-conductor not dispatching reviews / assignments¶
Symptom: No PR/CI notifications in Discord; agents not picking up issues. (Pre-2026-05-20 this was the "ATL-E not posting" section — ATL-E was retired and replaced by rig-conductor + rig-planner.)
Diagnose:
gcloud compute ssh invotek-k3s --zone=europe-north1-b --project=invotek-github-infra
kubectl get pods -n rig-conductor
kubectl logs -n rig-conductor deploy/rig-conductor-api --tail=200
# Health probe via cluster DNS (ClusterIPs aren't stable across recreates):
kubectl run -n rig-conductor probe --rm -it --image=curlimages/curl -- \
curl -sS http://rig-conductor-api.rig-conductor.svc.cluster.local:8080/api/health
Webhook delivery failing?
Check the org-level webhook deliveries for https://rig-conductor.dashecorp.com/api/webhook/github (requires admin:org_hook scope on the gh CLI token):
Argo CD out of sync?
kubectl patch application rig-conductor -n argocd --type merge \
-p '{"metadata":{"annotations":{"argocd.argoproj.io/refresh":"hard"}}}'
GitHub App Token Issues¶
401 Unauthorized / Bad credentials in agent API calls¶
Symptom: Agent's GitHub API calls fail with 401. Usually happens after 1 hour.
Cause: GitHub App installation tokens expire after 1 hour.
Fix (manual refresh):
# On the agent host — script lives at ~/.config/github-apps/get-token.py
# (the credential helper always resolves via $HOME/.config/github-apps/)
TOKEN=$(python3 ~/.config/github-apps/get-token.py pie-agent-bot Stig-Johnny)
# Update gh CLI hosts.yml
python3 -c "
import yaml, sys, os
with open(os.path.expanduser('~/.config/gh/hosts.yml')) as f: config = yaml.safe_load(f)
config['github.com']['oauth_token'] = sys.argv[1]
with open(os.path.expanduser('~/.config/gh/hosts.yml'), 'w') as f: yaml.dump(config, f)
" "$TOKEN"
Long-term fix: Container restart re-runs the entrypoint which refreshes tokens automatically.
git push fails with authentication failed¶
Symptom: fatal: Authentication failed on push.
Cause: The credential helper is using an expired or missing token.
Fix:
# Regenerate token and update remote URL
TOKEN=$(python3 ~/.config/github-apps/get-token.py pie-agent-bot Stig-Johnny)
git remote set-url origin "https://x-access-token:${TOKEN}@github.com/Stig-Johnny/REPO.git"
git push
PEM file not found¶
Symptom: get-token.py exits with FileNotFoundError: pie-agent-bot.pem not found.
Fix: Check PEM paths (resolves via $HOME, so depends on which user the agent runs as):
ls -la ~/.config/github-apps/*.pem
# Pi-E (user: claude): /home/claude/.config/github-apps/pie-agent-bot.pem
# Volt-E (user: root): /root/.config/github-apps/volt-e-agent-bot.pem
If missing, search for entry name pie-agent-bot in the Invotek vault — it has the PEM attached. Recover manually or via bw CLI:
declare -A error in get-token.sh (Mac only)¶
Symptom: declare: -A: invalid option on macOS.
Cause: macOS ships bash 3.2 which doesn't support associative arrays.
Fix: Use the Python script instead (path is consistent across agents):
Claude OAuth Token Expiry¶
Agent says "Authentication required" or stops responding¶
Symptom: Agent posts an auth error, or the agent log shows No authentication token found.
Background: Agents authenticate with a single 1-year Claude Max OAuth token (generated via claude setup-token, stored in Bitwarden as "Claude Code OAuth Token (1-Year, Mac Mini)", expires 2027-03-26, deadline tracker #17). There is no automated push/sync or keepalive cron. A monitoring-only watchdog on Pi-E (token-watchdog.sh, cron every 5 min) only alerts on expiry; it never writes tokens.
Primary fix: The token rarely expires within its 1-year life. If an agent reports an auth error, rotate the 1-year token per the deploy-agent-runner skill (Phase 8: Claude Auth) and reload the service.
Manual fix: rotate the single 1-year OAuth token per the deploy-agent-runner skill (Phase 8: Claude Auth) — regenerate with claude setup-token, store it in Bitwarden + the agent's env, then reload the service (Linux: sudo systemctl restart agent-runner; Mac: launchctl unload/load ~/Library/LaunchAgents/ai.invotek.<agent>.plist).
Emergency fix (VNC):
1. Open screen sharing://100.107.48.17 (Pi-E) or 100.111.142.118:PORT (Volt-E)
2. Open a terminal on the host (agent-runner runs as a host process, not a container)
3. Run claude setup-token → follow the browser flow, then reload the service
Discord Bot Issues¶
Bot offline indicator in server member list¶
Symptom: Bot shows as offline in Discord.
Cause: Discord token invalid or agent container/service not running.
Fix:
1. Check if the agent is running (see Agent Offline)
2. If running but bot still offline, token may have been rotated. Reset token in Discord Developer Portal, update Bitwarden, then update the token on the agent host and restart the agent — see How to Recreate a Bot Token for the exact per-runtime steps (agent-runner .env/.env.<agent>, or rig-gitops SOPS for Review-E).
Bot missing Message Content Intent error in logs¶
Symptom: Bot receives messages but body is empty; message.content is always "".
Fix: 1. Go to Discord Developer Portal → application → Bot tab 2. Enable Message Content Intent under Privileged Gateway Intents 3. Save (no restart needed — intent propagates within seconds)
Mentions not working in workflows¶
Symptom: @Pi-E in a workflow or script doesn't ping the bot.
Fix: Use the Bot User ID format: <@1477270576765735024>. Plain @Pi-E does not work programmatically. See docs/infrastructure/agents.md for all bot IDs.
CI / GitHub Actions Failures¶
YAML Lint — "too many spaces inside braces"¶
Symptom: CI fails with [error] too many spaces inside braces (braces) in a workflow file.
Fix: Change ${{ expression }} → remove extra spaces: ${{ expr }}. The yamllint braces rule requires exactly zero spaces inside { and } in YAML mappings (GitHub Actions template syntax is an exception but yamllint flags it).
To disable for a specific file, add to the YAML lint config:
Markdown Lint — MD025 multiple H1 headings¶
Symptom: MD025/single-title/single-h1 Multiple top-level headings on a docs file.
Cause: File has a YAML front matter block with a title field AND a # Heading at the top — markdownlint counts both.
Fix: Either remove the front matter title field, or use ## Heading (H2) for the first markdown heading.
Auto-merge PR check fails¶
Symptom: Auto-merge PR check fails, blocking merge.
Cause: This check depends on all other checks passing. It is a downstream indicator, not a root cause.
Fix: Look at the other failing checks first (lint, tests, security scan). Auto-merge will succeed once all required checks pass.
iOS Unit Tests fail with "Package.resolved out of date"¶
Symptom: CI fails: Package.resolved is out of date! Xcode Cloud will fail.
Fix:
cd ios
xcodegen generate
xcodebuild -resolvePackageDependencies -project RewardE.xcodeproj -scheme RewardE \
-skipPackagePluginValidation CODE_SIGNING_ALLOWED=NO
cp RewardE.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved Package.resolved
git add Package.resolved
git commit -m "chore: update Package.resolved"
release-please PR stuck / not updating¶
Symptom: Release PR (#N) was merged but a new one wasn't created, or the version wasn't bumped.
Fix:
1. Check release-please workflow run logs for the last push to main
2. Ensure release-please-manifest.json and release-please-config.json exist and are valid
3. If releases are stuck at an old version, manually push a chore: release-please trigger commit
Docker Container Issues¶
Container won't start — Permission denied on workspace files¶
Symptom: An agent fails to write to its workspace; logs show permission denied accessing files under ~/repos/claude-3/ (e.g. /home/claude/repos/claude-3/).
Cause: Workspace files are owned by a different user than the one the agent-runner process runs as (e.g. the repo was cloned or a command was run as root, but the service runs as claude). Under agent-runner there is no container — the agent runs as a host process, so files must be owned by the agent's host user.
Fix:
# On the host (as the agent user or root). Pi-E runs as `claude`:
sudo chown -R claude:claude /home/claude/repos/claude-3
sudo systemctl restart agent-runner
git: detected dubious ownership errors¶
Symptom: git commands fail with detected dubious ownership in repository.
Fix:
Add the specific repo path. This happens when the repo was cloned as root but git runs as node (1000).
Git / Push Errors¶
Force push protection error¶
Symptom: remote: error: GH006: Protected branch update failed, force push is not allowed.
Fix: Never force-push. Rebase or merge instead. If you've accidentally amend-committed a pushed branch, create a new commit with the correction.
fatal: unable to access / TLS handshake timeout¶
Symptom: Git operations time out on the Pi.
Cause: Network interruption, or Tailscale not connected.
Fix:
Markdown / YAML Lint Failures¶
Quick fix for common markdownlint errors¶
| Error | Fix |
|---|---|
| MD025: Multiple H1 | Use ## for first body heading if front matter has title: |
| MD012: Multiple blank lines | Remove extra blank lines (max 1 consecutive) |
| MD009: Trailing spaces | sed -i 's/ \+$//' file.md |
| MD013: Line too long | Wrap at 120 chars or add <!-- markdownlint-disable MD013 --> |
| MD041: First line not H1 | Add front matter title: or make first line # Title |
Disable markdownlint for a block¶
<!-- markdownlint-disable MD025 -->
# Second heading that would trigger MD025
<!-- markdownlint-enable MD025 -->
Escalation¶
| Issue | Escalate to |
|---|---|
| Claude OAuth token can't be refreshed | Stig-Johnny (VNC login or new token push) |
| GitHub App PEM lost | Stig-Johnny (Bitwarden recovery) |
| Tailscale node disconnected (Pi offline) | Stig-Johnny (physical access or reboot) |
| Production data concern | Stig-Johnny immediately |
| Bitwarden vault access lost | Stig-Johnny (recovery codes) |
| App Store review rejected | Stig-Johnny (human decision required) |
Post in #admin with: what's broken, what you tried, error message/logs.
Rig dispatch silently broken — symptoms & cleanup (2026-05-09)¶
The rig can look healthy (pods running, fresh heartbeats, queues empty) while actually being dead at the dispatch layer — agents sit idle, agent-ready issues never reach an executor, no PRs ship for days.
Quick triage (under 2 minutes)¶
# 1) How many agent-ready issues across the rig?
for repo in dashe-reward-e dashe-fast-e dashe-nutri-e dashe-count-e dashe-drink-e dashe-heart-e \
rig-agent-runtime rig-gitops rig-conductor rig-docs infra; do
hits=$(gh issue list --repo dashecorp/$repo --state open --label "agent-ready" --json number --jq 'length')
[ "$hits" -gt 0 ] && echo " $repo: $hits"
done
# 2) Last agent-authored PR — should be < 24h on a healthy rig
gh pr list --repo dashecorp/dashe-reward-e --state all --limit 5 \
--json author,createdAt,title \
--jq '.[] | select(.author.login | test("dev-e|review-e|ibuild-e|planner")) | "\(.createdAt[0:10]) \(.title[0:60])"' | head -3
# 3) Stream depths + stuck consumers
gcloud compute ssh invotek-k3s --zone=europe-north1-b --command="
for s in assignments:dev-e-node assignments:dev-e-dotnet assignments:dev-e-python assignments:review-e assignments:rig-planner assignments:ibuild-e; do
len=\$(kubectl -n rig-conductor exec valkey-primary-0 -- valkey-cli XLEN \$s)
pen=\$(kubectl -n rig-conductor exec valkey-primary-0 -- valkey-cli XPENDING \$s agents 2>&1 | head -1)
echo \"\$s len=\$len pending=\$pen\"
done
"
A healthy rig: agent-ready issues processed within minutes, recent agent PRs visible, queue depth ≤ 1, pending = 0 on every stream.
Symptom: stuck consumers (XPENDING > 0 with idle in hours/days)¶
Cause: an agent consumer claimed a stream message and was killed/restarted before ACK. Typical culprit is slo-reclaimer — a watchdog that reclaims stuck messages but can itself get stuck.
Fix:
gcloud compute ssh invotek-k3s --zone=europe-north1-b --command="
for s in assignments:dev-e-node assignments:dev-e-dotnet assignments:dev-e-python \
assignments:review-e assignments:rig-planner assignments:ibuild-e; do
kubectl -n rig-conductor exec valkey-primary-0 -- valkey-cli XPENDING \$s agents - + 50 2>&1 \
| awk 'NR%4==1 && /^[0-9]/{print \$1}' \
| while read id; do
[ -n \"\$id\" ] && kubectl -n rig-conductor exec valkey-primary-0 -- valkey-cli XACK \$s agents \$id
done
done
"
Symptom: empty-payload assignments published in a tight loop¶
Stream entries with repo="", issueNumber=0, title="", body="". Agents pull them, claude-cli returns category=idle, $0.05–0.08 burned per turn.
Cause: corrupt mt_doc_issuestatus rows in Postgres — ReconciliationService and SloEnforcerService keep republishing them every tick. Common origin: an event lands on an issue stream before any ISSUE_APPROVED (e.g., an EpicDecomposed event or webhook race), Marten's projection auto-creates a default-empty IssueStatus and Apply methods set state=queued without populating Repo/IssueNumber.
Find them:
gcloud compute ssh invotek-k3s --zone=europe-north1-b --command="
kubectl -n rig-conductor exec postgres-0 -- psql -U conductor -d rig_conductor -c \"
SELECT id, data->>'State' as state, COUNT(*) over() as total
FROM mt_doc_issuestatus
WHERE length(COALESCE(data->>'Repo','')) < 5 OR (data->>'IssueNumber')::int = 0;
\"
"
Delete them (safe — they have no real Repo/IssueNumber to link back to GitHub):
gcloud compute ssh invotek-k3s --zone=europe-north1-b --command="
kubectl -n rig-conductor exec postgres-0 -- bash -c \"psql -U conductor -d rig_conductor -c \\\"
DELETE FROM mt_doc_issuestatus
WHERE length(COALESCE(data->>'Repo','')) < 5 OR (data->>'IssueNumber')::int = 0;
\\\"\"
"
Then trim the assignment streams to clear the published backlog:
gcloud compute ssh invotek-k3s --zone=europe-north1-b --command="
for s in assignments:dev-e-node assignments:dev-e-dotnet assignments:dev-e-python \
assignments:review-e assignments:rig-planner; do
kubectl -n rig-conductor exec valkey-primary-0 -- valkey-cli XTRIM \$s MAXLEN 1
done
"
Defensive fix tracked in dashecorp/rig-conductor#716.
Symptom: KEDA ScaledObject READY: False¶
Look at kubectl get scaledobject -A. If READY=False with event message ScaledObject doesn't have correct Idle/Min/Max Replica Counts specification, KEDA stops managing the StatefulSet — replicas revert to chart default (often 1) — pods you thought were paused stay running.
KEDA validation requires maxReplicaCount >= 1. To pause a deployment, don't set max=0 — use the paused-replicas annotation:
metadata:
name: <so-name>
annotations:
autoscaling.keda.sh/paused-replicas: "0"
spec:
minReplicaCount: 0
maxReplicaCount: 1 # KEDA-valid; effective replicas pinned to 0 by annotation
To unpause: remove the annotation.
Symptom: planner files children that get re-decomposed forever¶
Pattern: planner files parent epic + N children correctly. Then planner re-runs on each child and files M sub-children of those, ad infinitum. Caused by:
- Conductor dispatches every
agent-readyissue to planner first. Children get re-dispatched. - Planner's "epic intent" detector fires on child bodies (multiple acceptance criteria, mention of "epic" in the parent reference).
- No self-author guard.
Fixed by rig-agent-runtime#275 (self-author short-circuit) + #278 (child-labeling fires issues.labeled webhook so dispatch goes to dev-e not back to planner). If symptom recurs, verify the planner image SHA pinned in rig-gitops includes both PRs.
Symptom: TestFlight shows wrong build (e.g. operator's last seen build is older than several uploaded ones)¶
Several causes today:
| Visible | Real |
|---|---|
| TestFlight defaults to highest build number | The highest may be a stale Xcode Cloud build (pre-migration). Tap version → pick the build that came from build-and-upload.yml |
Builds uploaded but not in Testers group |
Apple's auto-distribution from hasAccessToAllBuilds=True is best-effort, not real-time. Verify with: gh api ... (see appstoreconnect skill) |
Tester state=INVITED not INSTALLED |
Their invite was sent but never accepted. Re-send via API (see appstoreconnect). |
Disable any monitor-xcode-cloud.yml workflow on a repo migrated to GitHub-Actions iOS CI — it'll keep alarming on absent Xcode Cloud builds.
Symptom: build-and-upload.yml succeeded but TestFlight build never appeared¶
altool returns exit 0 even on ENTITY_ERROR.ATTRIBUTE.INVALID.DUPLICATE (build number collides with an existing build). The fix landed 2026-05-09: dashe-reward-e's workflow now greps altool output for ENTITY_ERROR / UPLOAD FAILED / Validation failed and exits non-zero. Build number is auto-incremented from the latest in ASC instead of relying on project.yml's CURRENT_PROJECT_VERSION (which drifts).
If you see a duplicate-build error today: it's almost always two concurrent workflow runs (auto-trigger from merge + manual workflow run) racing. Cancel one.
Symptom: iBuild-E "completed" with 0 turns and no PR¶
Two failure modes today, both same outward signature:
-
Auth failure (claude-cli reports
Invalid API keythen exits clean). Plist'sANTHROPIC_API_KEYis the leaked/revoked one. Seeibuild-eskill — fix issudo -n launchctl bootout/bootstrapafter replacing the key. -
Workspace fallback (claude-cli runs many turns in
~/repos/star-rewards/instead ofdashecorp/dashe-reward-e, ends without ability to push).task-workspacescript missing orWORKSPACE_ROOTnot in plist. Seeibuild-eskill for required setup.
gcloud compute ssh to GCP rig host¶
If a runbook tells you to SSH to 100.126.108.70 and you don't have a key — use:
The operator's gcloud OAuth is configured against project invotek-github-infra. No SSH key needed; gcloud injects a temporary key. Don't waste time on ssh root@100.126.108.70 (no authorized key) or tailscale ssh (not enabled on the node).
Long-term cleanup tracked in dashecorp/infra#219.