Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
ef31cac
Replace the always-on runtime-backup service with the opt-in github-s…
joshalbrecht Jul 12, 2026
ba87186
Fix post-commit hook visibility halt defeated by jq's // operator
joshalbrecht Jul 12, 2026
5b99f11
Clarify visibility-halt docs to match the keep-last-confirmed semantics
joshalbrecht Jul 12, 2026
5b1172b
Normalize repo_url in the hook's secondary-gateway fallback
joshalbrecht Jul 12, 2026
4350ce9
Remove trailing blank lines left in manager_test.py
joshalbrecht Jul 12, 2026
8088b6f
Write the github-sync status file atomically
joshalbrecht Jul 12, 2026
d9a0783
Normalize repo_url trailing slash before the .git suffix
joshalbrecht Jul 12, 2026
21c96ad
Clarify the post-commit hook's skip-case comment
joshalbrecht Jul 12, 2026
df63b4f
Test _do_tick's self-healing worktree restore and deferral
joshalbrecht Jul 12, 2026
6380b13
Validate the sync repo URL's owner/repo shape at load time
joshalbrecht Jul 12, 2026
52d5db1
Test the visibility re-check caching and keep-last-confirmed policy
joshalbrecht Jul 12, 2026
88daf04
Align the visibility.py docstring with the actual halt policy
joshalbrecht Jul 12, 2026
29fe66e
Terminate init_runtime_worktree's branch-setup chain with an explicit…
joshalbrecht Jul 12, 2026
daaa9c5
Rename local_branch_exists to has_local_branch for symmetric naming
joshalbrecht Jul 12, 2026
88876d5
Format libs/github_sync with the repo's ruff formatter
joshalbrecht Jul 12, 2026
7fb7b2a
Reset visibility state when the configured sync repo changes
joshalbrecht Jul 12, 2026
b2a95b3
Correct the bootstrap comment about insteadOf rewrite chaining
joshalbrecht Jul 12, 2026
8a04e69
Clarify that use-inspiration's commit push is gated on GitHub sync
joshalbrecht Jul 12, 2026
0ed2415
Update show-files-in-chat's runtime/ durability wording for opt-in sync
joshalbrecht Jul 12, 2026
15760a9
Disable TTY credential prompts in the post-commit auto-push hook
joshalbrecht Jul 12, 2026
5c47203
Ensure the secrets .gitignore exists on every runtime worktree init path
joshalbrecht Jul 12, 2026
147293f
Fix github-sync enable flow to request github-write-all for repo crea…
Jul 14, 2026
18c70a9
Make the github-sync permission ask correct and self-sufficient
joshalbrecht Jul 14, 2026
c735b74
Note the permission-ask correction in the changelog
joshalbrecht Jul 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"cloudflare-tunnel",
"app-watcher",
"bootstrap",
"runtime-backup",
"github-sync",
"host-backup",
"terminal",
"deferred-install",
Expand Down
2 changes: 1 addition & 1 deletion .agents/skills/edit-services/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Key fields:
instead.

Services inherit the agent environment (`MNGR_AGENT_STATE_DIR`,
`CLAUDE_CONFIG_DIR`, `MNGR_HOST_DIR`, `GH_TOKEN`, ...) from the bootstrap shell
`CLAUDE_CONFIG_DIR`, `MNGR_HOST_DIR`, `LATCHKEY_*`, ...) from the bootstrap shell
that launched supervisord -- you do not need a per-program `environment=`.

## Adding a service
Expand Down
221 changes: 221 additions & 0 deletions .agents/skills/github-sync/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
---
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, auto-pushes every commit from every checkout, and continuously syncs runtime/ state (memory, tickets, transcripts). Use when the user asks to back up / sync the workspace to GitHub, enable auto-push, restore a previous workspace's state, or asks about GitHub sync status.
compatibility: Requires latchkey (see the latchkey skill) and the user approving GitHub permissions in the Minds app.
---

# GitHub sync

GitHub sync is opt-in. Nothing syncs until this skill enables it. Once
enabled, three pieces work together (see `libs/github_sync/README.md`):

1. `origin` points at a dedicated **private** GitHub repo for this workspace.
2. 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.
3. The `[program:github-sync]` service commits + pushes `runtime/` to the
`runtime-sync` orphan branch every 60s and re-verifies the repo stays
private, halting pushes if it ever isn't.

## 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 `parent.toml` (see the update-self skill) and are unaffected.

## Enable

1. **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).

2. **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 -- **fire both back-to-back, before any other GitHub call**, so
the user approves them in a single sitting. Never dribble out further
requests later in the flow.

```bash
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 and runtime state to your private sync repo."}'
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 **not** enough to create a repo. It also covers the
optional repo deletion on disable.
- `github-read-repos` -- the recurring private-visibility check
(`GET /repos/{owner}/{repo}`), which the service repeats forever.
- `github-read-user` -- `GET /user`, to name the account the repo will be
created under (step 3) and as the one legitimate "did the grants land?"
probe (below).
- `github-git-read` / `github-git-write` -- clone/fetch and push.

Then **wait for both approval system messages** ("Your permission request
for GitHub (git) / (REST API) was granted..."). Those messages are the
authoritative signal; they are what tells you to proceed.

**Do not treat a rejected API call as evidence that a grant is missing.**
`{"error": "Error: Request not permitted by the user."}` means *that
endpoint* is not covered by the granted permissions -- it does not mean
the approval failed to arrive. Probing an endpoint outside the set above
(e.g. `GET /user/repos`, which needs broader read access) will be rejected
even when everything is granted correctly. If you want to sanity-check
after the grant messages arrive, the only probe to use is:

```bash
latchkey curl -s https://api.github.com/user
```

Never re-ask the user for a permission you have already been told was
granted; re-read this list and check *which endpoint* you called instead.

3. **Pick the repo.** Default: create a brand-new private repo named after
the workspace (`$MINDS_WORKSPACE_NAME`), owned by the authenticated GitHub
account (read its `login` from `latchkey curl -s https://api.github.com/user`).
Confirm the name and owner with the user first; they can name an org
instead. One exception: if `git remote get-url origin` already points at a
user-owned repo (not `imbue-ai/default-workspace-template` or another
shared template), ask whether to reuse it or create a fresh one --
recommend a fresh dedicated repo unless they have a specific reason.
Reused repos must be verified private and writable like new ones.

4. **Create the repo** (skip if reusing):

```bash
latchkey curl -s -X POST https://api.github.com/user/repos \
-H 'Content-Type: application/json' \
-d '{"name": "<repo-name>", "private": true, "description": "Private sync repo for the <workspace> minds workspace"}'
```

(For an org: `POST https://api.github.com/orgs/<org>/repos`.) On a 422
name-taken error, append `-2`, `-3`, ... and retry. The response JSON must
contain `"private": true` -- if it does not, delete/abandon the repo and
stop; do not proceed with a public repo. The response's `full_name` is the
authoritative `<owner>/<repo>` to use from here on.

5. **Point origin at it and record the config**:

```bash
git remote set-url origin https://github.com/<owner>/<repo>.git \
|| git remote add origin https://github.com/<owner>/<repo>.git
```

Write `github_sync.toml` at the repo root (this file is the "sync is
enabled" marker for the service and the post-commit hook):

```toml
# Written by the github-sync skill. Presence of this file enables GitHub
# sync; `uv run github-sync status` reports on it.
repo_url = "https://github.com/<owner>/<repo>"
```

6. **Wire git through the gateway**: `uv run github-sync wire-git`. From now
on plain `git push`/`git fetch` against github.com works in every
checkout, and the post-commit auto-push hook is active.

7. **Create (or restore) the runtime worktree**:
`uv run github-sync setup-worktree`. If origin already has a
`runtime-sync` branch (re-enabling for a workspace recreated from a
previously-synced repo), this restores the prior runtime/ state instead of
starting fresh -- tell the user their memory/tickets/transcripts are back.

8. **Verify private before any push**: `uv run github-sync check-visibility`
must print `private` (exit 0). If not, stop and surface the problem.

9. **Initial sync**: push the current branch, the runtime-sync branch, and
any existing worker branches:

```bash
git push --set-upstream origin "$(git branch --show-current)"
git -C runtime push --set-upstream origin runtime-sync
for b in $(git for-each-ref --format='%(refname:short)' refs/heads/ | grep -v -x -e "$(git branch --show-current)" -e runtime-sync); do git push origin "$b"; done
```

10. **Add the service** by appending this block to `supervisord.conf`, then
`supervisorctl reread && supervisorctl update` (see the edit-services
skill):

```ini
# Opt-in GitHub sync (added by the github-sync skill): commits + pushes
# runtime/ to the runtime-sync branch of the private sync repo and
# re-verifies the repo stays private. See libs/github_sync/README.md.
[program:github-sync]
command=uv run github-sync run
directory=/mngr/code
autostart=true
autorestart=true
startretries=1000000
stopasgroup=true
killasgroup=true
stdout_logfile=/var/log/supervisor/github-sync-stdout.log
stderr_logfile=/var/log/supervisor/github-sync-stderr.log
stdout_logfile_maxbytes=10MB
stderr_logfile_maxbytes=10MB
stdout_logfile_backups=3
stderr_logfile_backups=3
```

11. **Commit the enablement** (`github_sync.toml` + `supervisord.conf`). The
now-active hook pushes the commit; this also makes sync sticky if the
repo is later used to recreate the workspace.

12. **Report**: the repo URL, that every commit now auto-pushes, that
runtime/ syncs every minute, and that pushes queue while their machine
(the latchkey gateway) is offline (on remote hosts the per-VPS secondary
gateway usually covers that).

## Status

`uv run github-sync status` prints config + the service's latest status
(visibility, last push, 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
`github_sync.toml` and the service block, but not the latchkey permissions or
the container-local wiring/worktree. The service idles until it can reach
origin. To repair: run step 2 (permission requests); the service self-heals
within a tick (re-wires git, restores runtime/ from origin). Verify with
"Status", or accelerate with `uv run github-sync wire-git` +
`uv run github-sync setup-worktree`.

## Disable

Confirm with the user first, and ask separately whether to keep the remote
repo (recommend keeping it -- it costs nothing and preserves history).

1. `supervisorctl stop github-sync`, remove the `[program:github-sync]` block
from `supervisord.conf`, then `supervisorctl reread && supervisorctl update`.
2. `uv run github-sync unwire-git` (removes the gateway git config and the
hooks path -- auto-push stops).
3. Delete `github_sync.toml`.
4. Leave the local `runtime/` worktree and its history intact (harmless, and
re-enabling picks it right back up).
5. 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.
6. Commit the removal. Note that this commit is NOT auto-pushed (the hook is
inert again).
2 changes: 1 addition & 1 deletion .agents/skills/publish-inspiration/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,7 @@ exit 1

If the user never approves, surface a clear message and stop, leaving the
assembled commit intact. Do NOT fall back to any other credential or
mechanism (no `GH_TOKEN`-in-URL pushes, no partial-tree API uploads -- see
mechanism (no token-in-URL pushes, no partial-tree API uploads -- see
the "MUST BE BOOTABLE" callout).

## 8. Create the repo and push
Expand Down
3 changes: 2 additions & 1 deletion .agents/skills/show-files-in-chat/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ any other file is offered as a download.

1. Write the image to disk under `runtime/chat-images/` (create it once with
`mkdir -p runtime/chat-images` if it does not exist). That directory is
gitignored and is backed up along with the rest of `runtime/`.
gitignored and persists with the rest of `runtime/` (covered by the opt-in
GitHub sync when the user has enabled the `github-sync` skill).

Give each image a unique, descriptive filename, e.g.
`revenue-by-quarter-2026.png`. Served image URLs are cached immutably, so
Expand Down
5 changes: 3 additions & 2 deletions .agents/skills/use-inspiration/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,8 @@ one never removes or overwrites the manifests of the others.

## 7. Commit

Commit the adaptation per the repo's git conventions (a plain local commit; the
post-commit hook handles any push). Include the merged-in tree, the modified
Commit the adaptation per the repo's git conventions (a plain local commit;
when the user has enabled GitHub sync, the post-commit hook handles any push).
Include the merged-in tree, the modified
files from filling holes, and the updated manifest with its new `Adaptation
history` entry.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@

# Default Workspace Template runtime state.
# memory/ now lives at runtime/memory/ (configured via .claude/settings.json's
# autoMemoryDirectory) so the runtime-backup service backs it up alongside
# the rest of runtime/.
# autoMemoryDirectory) so the opt-in github-sync service (see the github-sync
# skill) covers it alongside the rest of runtime/.
**/runtime/

# chat attachment uploads (arbitrary size/format; kept out of version control)
Expand Down
Loading