automated-updates
How the devbox automatically updates llm-agents (claude-code) via GitHub Actions and systemd timers. Use when debugging update failures or understanding the update flow.
SKILL.md.claude/skills/automated-updates/SKILL.md
GitHubAutomated Updates
The devbox keeps dependencies up to date via GitHub Actions workflows and a systemd timer.
What Gets Updated
Local packages (daily)
- beads: Distributed issue tracker for AI workflows
Updated by .github/workflows/update-packages.yml using nix-update. Package definitions live in pkgs/<name>/default.nix.
How It Works
┌─────────────────────────────────────────────────────────────┐
│ GitHub Actions │
│ │
│ Local packages (daily): │
│ update-packages.yml → nix-update → PR → auto-merge │
│ │
│ CI (ci.yml) runs nix flake check on each PR │
│ ↓ │
│ Checks pass → PR auto-merges to main │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Devbox (systemd timer, every 4 hours) │
│ │
│ 1. pull-workstation.timer triggers │
│ 2. Fetches origin/main │
│ 3. If updates: git pull --ff-only │
│ 4. Runs: home-manager switch --flake .#dev │
└─────────────────────────────────────────────────────────────┘
Components
| Component | Location | Purpose |
|---|---|---|
ci.yml |
.github/workflows/ |
Runs nix flake check on PRs |
update-packages.yml |
.github/workflows/ |
Updates local packages via nix-update |
UPDATE_TOKEN |
GitHub Secrets | PAT for PR creation + CI triggers |
pull-workstation |
~/.local/bin/ |
Script to pull + apply home-manager |
pull-workstation.timer |
systemd user | Triggers every 4h + 10min after boot |
home-manager-auto-expire.timer |
systemd user | Cleans old generations daily |
Adding a New Package
- Create
pkgs/<name>/default.nixwith the derivation - Add to
localPkgsForinflake.nix - Reference as
localPkgs.<name>inhome.base.nix - Add
<name>to the matrix in.github/workflows/update-packages.yml
Checking Status
GitHub Side
# Recent workflow runs
gh run list --workflow=update-packages.yml --limit=5
# Open update PRs
gh pr list --label automated
# CI status on a PR
gh pr checks <pr-number>
Devbox Side
# Timer status
systemctl --user status pull-workstation.timer
systemctl --user status home-manager-auto-expire.timer
# When timers will next run
systemctl --user list-timers
# Recent pull-workstation runs
journalctl --user -u pull-workstation -n 50
# Recent auto-expire runs
journalctl --user -u home-manager-auto-expire -n 50
Manual Trigger
Trigger GitHub Update
gh workflow run update-packages.yml
# Or update a single package:
gh workflow run update-packages.yml -f package=beads
Trigger Devbox Pull
~/.local/bin/pull-workstation
Or via systemd:
systemctl --user start pull-workstation
Troubleshooting
PR not being created
- Check workflow ran:
gh run list --workflow=update-packages.yml --limit=1 - Check for errors:
gh run view <run-id> --log - Verify
UPDATE_TOKENsecret exists:gh secret list
PR not auto-merging
- Check CI passed:
gh pr checks <pr-number> - Check auto-merge is enabled:
gh pr view <pr-number> - Check branch protection: Settings → Branches → main
Devbox not pulling updates
- Check timer is active:
systemctl --user status pull-workstation.timer - Check for dirty working tree:
git -C ~/projects/workstation status - Check logs:
journalctl --user -u pull-workstation -n 50 - Manual test:
~/.local/bin/pull-workstation
"Working tree not clean" error
The pull script refuses to run if there are uncommitted changes:
cd ~/projects/workstation
git status
# Either commit, stash, or discard changes
SSH errors in pull-workstation
The script uses BatchMode=yes which fails if:
- SSH key missing: Check
~/.ssh/id_ed25519_githubexists - Host key missing: Run
ssh -T git@github.comonce manually
Old generations piling up
Check auto-expire is running:
systemctl --user status home-manager-auto-expire.timer
journalctl --user -u home-manager-auto-expire -n 20
Manual cleanup:
home-manager expire-generations "-7 days"
nix-collect-garbage
Configuration
Update Frequency
Local packages: Edit .github/workflows/update-packages.yml:
schedule:
- cron: '0 6 * * *' # Daily at 06:00 UTC
Devbox timer: Edit users/dev/home.devbox.nix:
Timer = {
OnStartupSec = "10min";
OnUnitInactiveSec = "4h"; # Change to desired interval
};
Generation Retention
Edit users/dev/home.devbox.nix:
services.home-manager.autoExpire = {
frequency = "daily";
timestamp = "-7 days"; # Keep generations from last 7 days
};