| name | github-sync |
| description | Enable, check, or disable GitHub sync for this workspace. Enabling creates a dedicated PRIVATE GitHub repo via latchkey, points origin at it, and auto-pushes every commit from every checkout. Workspace data under data/ is NOT synced to GitHub (the restic host backup covers it). Use when the user asks to back up / sync the workspace to GitHub, enable auto-push, or asks about GitHub sync status. |
| compatibility | Requires latchkey (see the latchkey skill) and the user approving GitHub permissions in the Minds app. |
| metadata | {"author":"imbue"} |
GitHub sync
GitHub sync is opt-in. Nothing syncs until this skill enables it. Once
enabled, three pieces work together (see system/libs/github_sync/README.md):
origin points at a dedicated private GitHub repo for this workspace.
- Global git wiring routes all
https://github.com/... traffic through the
latchkey gateway (credential injected server-side; no token in the
container) and activates the post-commit hook, so every commit on any
checkout -- main repo and worker worktrees -- auto-pushes its branch.
- The
[program:github-sync] service is a watchdog: every 60s it re-applies
the git wiring (self-healing gateway port changes) and re-verifies the repo
stays private, halting the hook's pushes if it ever isn't.
What is synced is exactly what is committed to git. Workspace data under
data/ (memories, tickets, uploads, per-app data) is gitignored and is
NOT shipped to GitHub -- the restic host-backup service covers it.
Hard rules
- Private repos only. Never create a public repo, never point sync at a
public repo, and never work around a visibility halt. If the user asks for
a public sync repo, decline and explain: agents can push secrets or other
sensitive data without realizing it.
- Everything flows through latchkey. Never ask the user for a GitHub
token and never embed credentials in URLs or git config.
origin is reserved for the sync repo. Upstream-template operations keep
using system/config/parent.toml (see the update-self skill) and are unaffected.
Enable
-
Check current state: uv run github-sync status. If is_configured
is already true, jump to "Status" (or "Repair" if the service is
unhealthy). Also run supervisorctl status github-sync (it errors when no
such program exists -- expected before enable).
-
Request GitHub permissions through latchkey (see the latchkey skill
for the permission-request mechanics). GitHub exposes two latchkey scopes
and a permission request carries exactly one scope, so this is two
requests. A request must be the only command in its tool call, so these
are two calls -- send them back to back; you do not have to wait for the
first verdict to file the second.
Do both before any other GitHub call, and say up front that two
approvals are coming so the user is not surprised by the second. Never
dribble out further requests later in the flow.
First call, on its own:
latchkey curl -XPOST http://latchkey-self.invalid/permission-requests \
-H 'Content-Type: application/json' \
-d '{"agent_id": "'"$MNGR_AGENT_ID"'", "type": "predefined", "payload": {"scope": "github-git", "permissions": ["github-git-read", "github-git-write"]}, "rationale": "GitHub sync: push this workspace'"'"'s branches to your private sync repo."}'
Then the second call, on its own:
latchkey curl -XPOST http://latchkey-self.invalid/permission-requests \
-H 'Content-Type: application/json' \
-d '{"agent_id": "'"$MNGR_AGENT_ID"'", "type": "predefined", "payload": {"scope": "github-rest-api", "permissions": ["github-read-user", "github-read-repos", "github-write-all"]}, "rationale": "GitHub sync: create the private sync repo (needs github-write-all), confirm which GitHub account it lands under (github-read-user), and verify it stays private (github-read-repos)."}'
This exact permission set is what the flow needs -- do not trim it, or the
user gets asked again mid-flow:
github-write-all -- repo creation (POST /user/repos). The narrower
github-write-repos covers only existing-repo (/repos/{owner}/{repo})
paths and is enough to create a repo. It also covers the
optional repo deletion on disable.
Status
uv run github-sync status prints config + the service's latest status
(visibility, errors); supervisorctl status github-sync shows the
process; logs are at /var/log/supervisor/github-sync-*.log and
/tmp/github-sync.log, hook output at /tmp/post-commit-push.log. Explain
findings in plain language. If is_push_allowed is false, the repo is public
or unverifiable -- tell the user to make it private again; sync resumes
automatically.
Repair (workspace recreated from a synced repo)
A workspace created from a previously-synced private repo inherits the
service block, but not the gitignored data/system/github_sync.toml, the
latchkey permissions, or the container-local wiring. To repair: re-write the
config file (step 5), then run step 2 (permission requests); the service
self-heals within a tick (re-wires git). Verify with "Status", or accelerate
with uv run github-sync wire-git. Workspace data under data/ comes back
via a restic backup restore, not via GitHub.
Disable
Confirm with the user first, and ask separately whether to keep the remote
repo (recommend keeping it -- it costs nothing and preserves history).
supervisorctl stop github-sync, remove the [program:github-sync] block
from system/supervisord.conf, then supervisorctl reread && supervisorctl update.
uv run github-sync unwire-git (removes the gateway git config and the
hooks path -- auto-push stops).
- Delete
data/system/github_sync.toml.
- If the user chose to delete the remote repo:
latchkey curl -s -X DELETE https://api.github.com/repos/<owner>/<repo>
(covered by the github-write-all granted at enable). If the grant has
since been revoked, do not re-request it just for this -- point them at
the repo's GitHub settings page to delete it themselves.
- Commit the removal. Note that this commit is NOT auto-pushed (the hook is
inert again).