AugmentClaude

Pilot AWS

Manage and monitor the AWS-hosted Pilot daemon with status, logs, and operational commands.

Installation

  1. Make sure Claude is on your device and in your terminal.

    Skills load from ~/.claude/skills/ when Claude Code starts up β€” so you need it on your machine first. If you don't have it yet, install it once with the command below, then run claude in any terminal to verify.

    One-time setup
    npm i -g @anthropic-ai/claude-code

    Already have it? Skip ahead.

  2. Paste into Claude Code or into your terminal.

    This copies the whole skill folder into ~/.claude/skills/pilot-aws-qf-studio/ β€” the SKILL.md plus any scripts, reference docs, or templates the skill ships with. Safe default: works for every skill.

    Faster alternative (instruction-only skills)

    Skips the clone and grabs only the SKILL.md file. Don't use this if the skill ships Python scripts, reference markdowns, or asset templates β€” they won't be downloaded and the skill will fail when it tries to load them.

    Quick install (SKILL.md only)
    Sign up to copy
  3. Restart Claude Code.

    Quit and reopen Claude Code (or any other agent that loads from ~/.claude/skills/). New skills are picked up on startup.

  4. Just ask Claude.

    Skills auto-activate when your request matches the skill's description β€” no slash command needed. Trigger phrases live in the skill's own frontmatter; you can read them in the β€œWhat this skill does” section above.

Prefer to read the source first? Open on GitHub.

When Claude uses it

Operate the AWS-hosted Pilot daemon (founder box) β€” status, dashboard, logs, queue queries, start/stop/restart, rebuild/upgrade, metrics tunnel, troubleshooting. Auto-invoke when user says "pilot aws", "check the box", "aws daemon", "pilot on aws", "box status", "restart pilot on aws", "box logs", or any operation against the hosted daemon.

What this skill does

Pilot AWS Operations (founder box)

Since the S6-lite cutover (2026-07-16, TASK-409) the Pilot daemon runs on an EC2 box, NOT locally. Local pgrep pilot returning 0 is correct.

Constants

INSTANCE   i-0e0c1ca34e7b561f9        # "pilot-founder-box", t3.xlarge, eu-central-1a
PROFILE    quantflow                   # export AWS_PROFILE=quantflow for every aws call
REGION     eu-central-1
RUNNER     i-0147f5c24d234cdbb         # mgmt runner (AdministratorAccess) β€” IAM/privileged ops only
DAEMON     tmux session "pilot" as ec2-user, wrapper /home/ec2-user/start-pilot.sh
BINARY     /var/lib/pilot/bin/pilot    # ec2-user-owned (self-upgrade works, #4470); /usr/local/bin/pilot is a SYMLINK to it β€” never `sudo install` over the symlink; rollback at pilot.prev
STATE      /home/ec2-user/.pilot β†’ /var/lib/pilot/pilot-home (200GB data volume)
REPOS      /Users/aleks.petrov/Projects β†’ /var/lib/pilot/repos (path shim β€” ledger keys
           on the macOS-era absolute paths; NEVER "fix" these symlinks)
DB         /home/ec2-user/.pilot/data/pilot.db
LOG        /home/ec2-user/.pilot/logs/daemon.log (+ daemon-stderr.log)

The SSM command pattern (used by everything below)

export AWS_PROFILE=quantflow AWS_DEFAULT_REGION=eu-central-1
CMD=$(aws ssm send-command --instance-ids i-0e0c1ca34e7b561f9 \
  --document-name AWS-RunShellScript \
  --parameters 'commands=["<shell here>"]' \
  --query Command.CommandId --output text)
sleep 10
aws ssm get-command-invocation --command-id $CMD \
  --instance-id i-0e0c1ca34e7b561f9 --query StandardOutputContent --output text

For multi-line/quote-heavy payloads write a JSON file and use --parameters file:///tmp/x.json; for shipping FILES to the box, base64 the content into the command (echo <b64> | base64 -d > target) β€” inline heredocs with \n escapes DO NOT survive SSM JSON (verified failure mode).

Operations

Status (first move, zero GitHub quota)

~/bin/pilot-board            # daemon health, queue, autopilot, log tail
~/bin/pilot-board --gh       # + GitHub issues/PRs (costs shared user quota β€” sparingly)

Remote half lives at /usr/local/bin/pilot-board-remote on the box (edit there).

Live TUI dashboard

~/bin/pilot-dash             # SSM interactive β†’ tmux attach -t pilot

Detach: Ctrl-B then D. NEVER press q or Ctrl-C inside the TUI β€” that stops the daemon. Fallback: plain ssm session β†’ sudo su - ec2-user β†’ tmux attach -t pilot.

Logs

# tail (adjust -n / add grep):
SSM: tail -50 /home/ec2-user/.pilot/logs/daemon.log
# common filters: 'rate limit', 'claim lost', 'approval', 'ERROR', a task id

Queue / ledger queries (read-only)

SSM: sqlite3 -column /home/ec2-user/.pilot/data/pilot.db \
  "SELECT task_id,status,datetime(created_at) FROM executions \
   WHERE status IN ('running','queued') ORDER BY created_at;"

Key tables: executions, execution_claims (task_id, project_path, generation), autopilot_pr_state, autopilot_scope_release, instance_events. ⚠️ Timestamp trap: pre-2026-07-16 rows may carry legacy …+02:00 string format β€” never filter by string time ranges across eras; use rowid or exact ids.

Stop / start / restart ⚠️ OPERATOR-CONSENT ACTIONS

Never do these on your own judgment during watch/autonomous modes; in interactive sessions get explicit user go-ahead. In-flight executions die (they retry via generation claims β€” proven safe, but wasteful).

# STOP    (graceful; wait, then verify 0):
SSM: sudo -iu ec2-user tmux send-keys -t pilot C-c   # TUI quit = graceful shutdown
     sleep 15; ps -eo comm | grep -c '^pilot$'        # must be 0
# START:
SSM: sudo -iu ec2-user tmux new-session -d -s pilot -x 220 -y 50 /home/ec2-user/start-pilot.sh
     sleep 20; ps -eo comm | grep -c '^pilot$'        # must be 1; then check pilot-board
# RESTART = STOP, verify, START, verify (banner version + no Telegram 409 + poller ticks).

After ANY start: verify version (/usr/local/bin/pilot version), exactly 1 process, curl -s localhost:9091/metrics | grep pilot_queue_depth (via SSM).

Rebuild / upgrade the box binary

SSM (as ec2-user, NO sudo β€” dir is ec2-user-owned since 2026-07-19):
  cd /Users/aleks.petrov/Projects/startups/pilot && \
  git fetch -q --tags origin main && git checkout -q <tag-or-origin/main> && \
  make build && install -m 0755 bin/pilot /var/lib/pilot/bin/pilot && \
  git checkout -q main && /var/lib/pilot/bin/pilot version

Then RESTART (above) to activate. Prefer building from a released tag. For releases, daemon self-upgrade also works now (#4470 preflight + writable dir) β€” letting it upgrade itself on the next train is the default path. Expect ~1 quiet hour post-restart until #4391 ships: startup rescans can burn the GitHub user-aggregate rate pool (see Troubleshooting).

Metrics tunnel (grafterm / local Prometheus tools)

~/bin/pilot-tunnel     # box:9091 β†’ localhost:9091, keep running; then `pilot-tui` (grafterm alias)

Box shell (interactive)

aws ssm start-session --target i-0e0c1ca34e7b561f9 --profile quantflow --region eu-central-1
sudo su - ec2-user

Privileged infra ops (IAM, volumes, instance-level)

User aleks lacks iam:PassRole/ec2:CreateVolume etc. Route through the mgmt runner via SSM RunShellScript on i-0147f5c24d234cdbb (it has admin). Scope every grant minimally; never print policy docs containing secrets.

Hard rules

  1. One daemon, ever. Never start pilot locally while the box serves the repos (dual-serve = cross-machine duplicate class; claims are per-DB). Rollback procedure lives in .agent/tasks/TASK-409-s6-lite-aws-cutover.md.
  2. Never touch the path shims (/Users/aleks.petrov/... symlinks on the box) β€” ledger + claims key on those exact strings.
  3. GitHub API is one shared per-USER pool (5000/hr) across every token, session, and the daemon. Prefer sqlite/metrics over gh for status. If the daemon logs "rate limit exceeded for user ID …" β€” it self-recovers on the rolling window; do not thrash retries. (#4391 tracks the durable fix.)
  4. Secrets: never echo tokens/keys; never ps full args of processes holding tokens; the wrapper script pattern exists precisely to keep tokens out of argv. Config on the box is verbatim-from-laptop; changes = operator consent
    • restart.
  5. Trust the ledger over dashboard panels (known mislabel: awaiting_approval rendered as "rebase" until the GH-4383 fix is in the running binary).
  6. Name your ledger. Every status/diagnosis claim must state which data source produced it: box DB (via SSM), GitHub, or the laptop archive. The laptop's ~/.pilot/data/pilot.db is a FROZEN pre-2026-07-16 archive (S6-lite cutover) β€” plausible-looking rows, months stale. A 2026-07-27 incident: a session read it and confidently misdiagnosed healthy tasks as "failed". Before reasoning about any DB: verify the path is the box's (/var/lib/pilot/pilot-home/data/pilot.db) and check row freshness (select max(datetime(created_at)) from executions). mem-160 family.

Troubleshooting quick table

SymptomLikely causeMove
Board says queue empty but daemon log shows executions; log claim generations exceed claims tablesplit-brain shadow ledger (#4393 class β€” daemon opened a DB at an unshimmed path)sudo readlink /proc/$(pgrep -x pilot)/fd/* must include /var/lib/pilot/pilot-home/data/pilot.db; if not: STOP daemon, locate+merge shadow DB, fix shim
ALL task executions fail unknown: exit status 1 after ~3m, 0 tokens, stream shows api_retry/fetch failed; judge/preflight children workRLIMIT_AS cap on executor children (#4401 class β€” GH-3028 "RSS cap", darwin no-op)grep 'address space' /proc/<claude-child>/limits must be unlimited; config subprocess_limits is enabled: false since 2026-07-17 (backup config.yaml.bak-4396) β€” OOM cap off until #4401
Queue frozen, pollers 403 "rate limit … user ID"user-aggregate GitHub pool exhausted (startup rescans, parallel sessions)wait for rolling window; stop nonessential gh usage; see #4391
Queue frozen, "dispatch claim lost" every poll, no 403sdead-owner non-terminal rows holding gen-N claims (post-restart/cutover)see #4392; workaround = mark orphan rows stalled with audit note (exact-id UPDATE, never string time-ranges) β€” this is a RECOVERY signal (retry the task), not a cancel; see next row if the goal is to stop a task
Need to permanently stop a task from ever being re-picked (dup ticket, wrong scope, abandoned, operator error)stalled is NOT a cancel verb β€” it means "dead owner, retry me" and the dispatcher grants it fresh generations exempt from the repick hard cap forever if hand-written for this purpose (GH-4655 incident)pilot task cancel <task-id> [--project <path>] [--reason "..."] (GH-4678) β€” the real terminal cancel; NEVER hand-write status='stalled' to try to stop a task
tmux session gone, no pilot processwrapper/script error at spawncheck daemon-stderr.log; verify wrapper intact (cat start-pilot.sh); restart per above
TUI monochromeTERM captured at daemon start~/.tmux.conf already sets 256color; colors return on next restart β€” do not bounce for paint
PR stuck "rebase N/3" in panelawaiting_approval mislabelledger: SELECT stage FROM autopilot_pr_state WHERE pr_number=N
Box unreachable via SSMagent/instance downaws ec2 describe-instances --instance-ids i-0e0c… β†’ LOUD escalate to operator; never assume
Multi-day uptime right after a train β€” looks like "installed but never restarted"NOT a stale process β€” self-upgrade's restart leg is syscall.Exec (PID preserved, so ps etime/uptime survives the restart; mem hot-restart-preserves-pid-uptime-false-mismatch). Since 2026-08-13 board ver reads the RUNNING process (pilot_build_info metric, #4864/PR#4865; repointed pilot-board-remote) β€” a (disk!) suffix means the daemon metric was unreachable and the value is the disk binary'sTrust ver (metric-sourced). Cross-check: curl -s localhost:9091/metrics | grep pilot_build_info via SSM, or grep 'upgrade verified complete' ~/.pilot/logs/daemon.log (boot-reconcile from=/to= logged by the NEW image; failure counterpart previous upgrade did NOT take effect). Do NOT bounce the daemon on uptime evidence alone

Refs

  • Cutover plan + rollback: .agent/tasks/TASK-409-s6-lite-aws-cutover.md
  • Restart discipline: .agent/sops/operations/safe-daemon-restart.md (verify PID+banner ALWAYS)
  • Open hardening: #4391 (rate-budget client), #4392 (orphan reconciliation)

Related skills