[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6C32TP4M01CKGMBR6KVTGZ2":3,"skill-same:01M6C32TP4M01CKGMBR6KVTGZ2":111},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":17,"publishedAt":48,"linkOnly":49,"markdownContent":50,"markdownBody":51,"frontMatter":52,"refName":29,"commitSha":55,"skillDirectory":56,"forksCount":57,"repositoryHtmlUrl":58,"repositoryHomepageUrl":6,"ownerHtmlUrl":59,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":60,"install":61,"resources":74},"01M6C32TP4M01CKGMBR6KVTGZ2","backup-folder-leak-purge",null,"Force-purge a launchd\u002Fcron-driven home-config backup folder (backup\u002F, snapshot\u002F, home-config\u002F) from a public GitHub repo, then prevent recurrence. Trigger when (a) `git push` keeps re-pushing personal home-dir content to a public repo every N hours, (b) `gh api ...\u002Fgit\u002Ftrees\u002Fmain?recursive=1` shows hundreds-to-thousands of paths under `backup\u002F` or similar, (c) a closed-PR scan reveals leaked credential\u002Fsecret dirs, or (d) you discover a `.gitignore` entry for backup\u002F that was bypassed via `git add -f`. Verified 2026-07-15 against jleechanorg\u002Fclaude-commands (491 MiB \u002F 6,820 files purged from origin\u002Fmain).","jleechanorg\u002Fjleechan-skills","jleechanorg","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F221404966?v=4","01M6C1VJ99XG950X1DWPEBCBGX","01M6C1VJDK5YVDNT3362RT5WSB",6,"MIT","https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills\u002Fblob\u002Fmain\u002Fhermes\u002Fskills\u002Fbackup-folder-leak-purge\u002FSKILL.md",[],[18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47],"purge","folder","backup","backup folder leak purge","launchd","2026","when","re","you","commands","discover","main","files","gitleaks-bypass","pre-push-hook","public-repo","github","snapshot","repo","public","gh","git push","api","gitleaks","data-leak","data","push","git","pr","config","2026-11-10 10:12:30",false,"---\nname: backup-folder-leak-purge\ndescription: Force-purge a launchd\u002Fcron-driven home-config backup folder (backup\u002F, snapshot\u002F, home-config\u002F) from a public GitHub repo, then prevent recurrence. Trigger when (a) `git push` keeps re-pushing personal home-dir content to a public repo every N hours, (b) `gh api ...\u002Fgit\u002Ftrees\u002Fmain?recursive=1` shows hundreds-to-thousands of paths under `backup\u002F` or similar, (c) a closed-PR scan reveals leaked credential\u002Fsecret dirs, or (d) you discover a `.gitignore` entry for backup\u002F that was bypassed via `git add -f`. Verified 2026-07-15 against jleechanorg\u002Fclaude-commands (491 MiB \u002F 6,820 files purged from origin\u002Fmain).\ntags: [security, git, github, launchd, data-leak, public-repo, pre-push-hook, gitleaks-bypass]\n---\n\n# Backup-folder leak purge\n\nA launchd\u002Fcron job is running a home-config backup script that rsyncs `~\u002F` into\na repo's `backup\u002F` (or similar) folder then `git push origin main`. Pre-existing\n`.gitignore` entries are bypassed by `git add -f`. The gitleaks pre-push hook\ndoesn't catch this class because it scans for secret patterns, not path prefixes.\n\nThis skill gives the full 6-phase recipe. Run phases IN ORDER — the leak\nresumes within minutes if you skip ahead.\n\n## When to use this skill\n\n- A repo's `origin\u002Fmain` has paths like `backup\u002F`, `snapshot\u002F`, `home-config\u002F`,\n  `private\u002F`, `secrets\u002F` that you did NOT intentionally add.\n- A `scripts\u002Fbackup-home.sh` (or similar) is run by launchd and pushes to `origin\u002Fmain`.\n- A user says \"stop this backup cron\", \"the folder shouldn't be in this repo\",\n  \"my home dir is on github\", \"backup-home.sh is leaking\", or asks about a PR\n  with thousands of unrelated file changes.\n- `gh api repos\u002F\u003Corg>\u002F\u003Crepo>\u002Fgit\u002Ftrees\u002Fmain?recursive=1 --jq '[.tree[]|select(.path|startswith(\"backup\u002F\"))]|length'` returns a non-zero number.\n\n## Failure pattern\n\n1. A `scripts\u002Fbackup-home.sh` rsyncs a curated subset of `~\u002F` into `$REPO\u002Fbackup\u002F\u003Chostname>\u002F`.\n2. The script commits with `git add backup\u002F && git commit -m \"backup: automated home config snapshot ...\"`.\n3. If local branch is main, `git push origin main` directly. Otherwise a\n   temporary worktree is created on main, the commit is staged there, and pushed.\n4. `.gitignore` has `backup\u002F` but the script uses `git add -f backup\u002F` to bypass.\n5. The pre-push gitleaks hook scans staged content for secrets — `backup\u002F` has\n   no secret patterns, so the push is allowed.\n6. Each launchd tick (every 1-12h) adds a new \"backup: ...\" commit, growing the\n   leak by ~hundreds of MiB per day.\n\n## Recipe — 6 phases (execute in order)\n\n**CRITICAL: clarify user intent FIRST.** A home-config backup script often has TWO modes:\n- **Local mode** (writes to `$REPO_ROOT\u002Fbackup\u002F\u003Chost>\u002F` on disk, never pushes)\n- **Push mode** (also `git push`es to origin)\n\nIf the script supports local-only mode (via `ALLOW_GIT_BACKUP_PUSH=0`), the user may want\nthat preserved — only the push needs to be disabled. **Do NOT assume they want the entire\ncron killed.** Verified 2026-07-15 — Jeffrey said \"I want the files in user_scope\u002Fbackup,\nNOT in claude-commands backup\" after I overcorrected by disabling the whole cron.\n\n**Generalized rule (apply to ANY destructive fix):** When the user reports a bug\n\"X is happening that shouldn't\", default to:\n1. Identify the *minimal* intervention that stops X\n2. Preserve everything else that was working\n3. **Never** delete\u002Fdisable a wider system as the primary fix unless the user\n   explicitly says \"kill it\" \u002F \"stop it completely\" \u002F \"take it down\"\n4. If the user's request is ambiguous, prefer reversible targeted fixes\n   (env var change, config flip, plist reload) over irreversible ones\n   (rename + chmod 000, `mv .disabled`, git reset --hard, force-push)\n5. After the targeted fix, run the `home-config-backup-audit` skill to\n   verify NO sibling landmines exist before declaring done\n\n**Intent probe before Phase 1:**\n- \"Should the cron keep running and just stop pushing?\" → Skip Phase 1+2, do Phase 2-ALT instead\n- \"Stop the cron entirely?\" → All 6 phases\n\n### Phase 2-ALT: Push-only disable (most common — local backups wanted)\n\nWhen the cron should keep running but never push:\n\n```bash\n# In ~\u002FLibrary\u002FLaunchAgents\u002F\u003Clabel>.plist AND its .template source:\n#   \u003Ckey>ALLOW_GIT_BACKUP_PUSH\u003C\u002Fkey>\n#   \u003Cstring>0\u003C\u002Fstring>   # was 1\n#\n# Keep ALLOW_GIT_BACKUP_SYNC=1 so local backups still write to $REPO_ROOT\u002Fbackup\u002F\u003Chost>\u002F\n\n# Reload to pick up new env\nlaunchctl bootout gui\u002F$(id -u) ~\u002FLibrary\u002FLaunchAgents\u002F\u003Clabel>.plist\nlaunchctl load -w ~\u002FLibrary\u002FLaunchAgents\u002F\u003Clabel>.plist\n\n# Verify\nlaunchctl print gui\u002F$(id -u)\u002F\u003Clabel> | grep ALLOW_GIT_BACKUP_PUSH\n# Expected: ALLOW_GIT_BACKUP_PUSH => 0\n```\n\nThe next launchd tick runs the rsync + local write step (because `ALLOW_GIT_BACKUP_SYNC=1`)\nbut skips `git commit`\u002F`git push` (because `ALLOW_GIT_BACKUP_PUSH=0`). User gets local\nbackup files; nothing leaves the machine.\n\n### Phase 1: Stop the cron immediately\n\nThe cron runs every N hours. If you delete from origin first, the next tick\nre-pushes and you're back at square one.\n\n```bash\nlaunchctl list 2>\u002Fdev\u002Fnull | grep -E \"backup|home-config\" | head   # find label\nlaunchctl bootout gui\u002F$(id -u) ~\u002FLibrary\u002FLaunchAgents\u002F\u003Clabel>.plist\nmv ~\u002FLibrary\u002FLaunchAgents\u002F\u003Clabel>.plist ~\u002FLibrary\u002FLaunchAgents\u002F\u003Clabel>.plist.disabled-\u003Cdate>\n```\n\nVerify: `launchctl print gui\u002F$(id -u)\u002F\u003Clabel>` returns \"Could not find service\".\n\n### Phase 2: Kill the script itself\n\nDefense in depth — even if launchd is resurrected, the script can't run.\n\n```bash\nmv scripts\u002Fbackup-home.sh scripts\u002Fbackup-home.sh.DISABLED-\u003Cdate>-leak\nchmod 000 scripts\u002Fbackup-home.sh.DISABLED-\u003Cdate>-leak\nmv scripts\u002F\u003Corg>.\u003Clabel>.plist.template scripts\u002F\u003Corg>.\u003Clabel>.plist.template.DISABLED-\u003Cdate>\n```\n\n### Phase 3: Find the clean reset target + force-push origin\u002Fmain\n\n```bash\n# First commit that touched backup\u002F (chronologically earliest)\nFIRST_LEAK_SHA=$(gh api \"repos\u002F\u003Corg>\u002F\u003Crepo>\u002Fcommits?path=backup&per_page=100\" \\\n    --jq 'sort_by(.commit.author.date) | .[0].sha')\n\n# Parent of that commit = clean tip\nPARENT_SHA=$(gh api \"repos\u002F\u003Corg>\u002F\u003Crepo>\u002Fcommits\u002F$FIRST_LEAK_SHA\" --jq '.parents[0].sha')\n\n# Reset + force-push\ngit -C \u003Clocal-clone> reset --hard \"$PARENT_SHA\"\ngit -C \u003Clocal-clone> push --force-with-lease origin main\n```\n\n**Target selection:** Often the clean tip is the commit that originally added\n`backup\u002F` to `.gitignore`. Resetting there keeps the gitignore defense in place\nAND drops every leak commit. Don't reset to a \"good\" backup-fix commit — those\nadded plumbing that may also need to be reverted if it shipped to main.\n\n### Phase 4: Verify origin is clean\n\n```bash\ngh api \"repos\u002F\u003Corg>\u002F\u003Crepo>\u002Fgit\u002Ftrees\u002Fmain?recursive=1\" \\\n    --jq '[.tree[] | select(.path | startswith(\"backup\u002F\"))] | length'\n# Expected: 0\n\ngh api \"repos\u002F\u003Corg>\u002F\u003Crepo>\u002Fcontents\u002Fbackup\"\n# Expected: 404 Not Found\n```\n\n### Phase 5: Prevent recurrence\n\n**5a. Pre-push hook** — See [`templates\u002Fpre-push-backup-block.sh`](templates\u002Fpre-push-backup-block.sh).\nInstall at `\u003Crepo>\u002F.git\u002Fhooks\u002Fpre-push` (chmod 755) in EVERY clone that could\npush to this repo (especially the one the cron writes from).\n\n**Critical:** stdin field order per `git\u002Fgithooks.adoc` is\n`\u003Clocal-ref> SP \u003Clocal-sha> SP \u003Cremote-ref> SP \u003Cremote-sha>`. Wrong order\nmakes the hook silently pass. See\n[`references\u002Fgit-hooks-pre-push-stdin-format.md`](references\u002Fgit-hooks-pre-push-stdin-format.md)\nfor the full bug analysis + 3-test verification harness.\n\n**Verify the hook actually works** — see\n[`scripts\u002Fverify-hook-blocks-backup-push.sh`](scripts\u002Fverify-hook-blocks-backup-push.sh).\nRun this BEFORE declaring the fix done. If Test 1 (forbidden path in main\npush) exits 0 instead of 1, the hook is silently broken — fix immediately.\n\n**5b. Watchdog launchd job** — See [`templates\u002Fbackup-leak-watchdog.sh`](templates\u002Fbackup-leak-watchdog.sh).\nRuns every 15 min, checks: (a) `ALLOW_GIT_BACKUP_PUSH` in active plist is `0`\n(or plist absent — cron disabled), (b) origin\u002Fmain has 0 forbidden paths.\nPosts to Slack on any anomaly with 6h cooldown.\n\n**Note:** The earlier version of this watchdog checked \"is the launchd label\nunloaded\" — that's wrong if the cron is supposed to keep running locally.\nThe correct check is \"is `ALLOW_GIT_BACKUP_PUSH=1`\", since the cron being\nloaded is fine as long as it's not pushing.\n\n**5c. Token gate** — The existing gitleaks pre-push hook should ALSO refuse\nthe push if `git add -f backup\u002F` was used. Add the path-prefix check\n(BEFORE the secret scan) to your pre-push hook so both run.\n\n### Phase 6: Local cleanup\n\n```bash\ndu -sh ~\u002Fprojects_other\u002F\u003Cuser_scope>\u002Fbackup\u002F   # was 8.2 GiB in our case\nrm -rf ~\u002Fprojects_other\u002F\u003Cuser_scope>\u002Fbackup\u002F\n\n# Other dormant worktrees — leave the branch HEAD alone (history preserved),\n# but the working-dir residue is harmless if the branch is local-only and not pushed\n```\n\n## Pitfalls\n\n1. **Don't delete the .gitignore entry.** Even though the leak bypassed it via\n   `git add -f`, the gitignore prevents future accidental `git add backup\u002F`\n   from working in the common case.\n\n2. **Don't reset to the latest backup-fix commit.** Those commits modified\n   `scripts\u002Fbackup-home.sh` to make it dropbox-only by default — but they were\n   the same broken pipeline that leaked. Reset to the commit BEFORE the first\n   backup-touching commit (often the .gitignore commit itself).\n\n3. **Pre-push stdin field order is `\u003Clocal-ref> SP \u003Clocal-sha> ...`.** Common\n   bug: `read -r local_sha local_ref remote_sha _` swaps SHA and ref, hook\n   silently passes. See\n   [`references\u002Fgit-hooks-pre-push-stdin-format.md`](references\u002Fgit-hooks-pre-push-stdin-format.md).\n\n4. **gitleaks pre-push hooks don't catch path-prefix leaks.** They scan for\n   secret patterns (apiKey, tokens, etc.), not path prefixes. Add a path-prefix\n   check alongside the secret scan, not as a replacement.\n\n5. **The leak could happen in branches too.** The hook only enforces on\n   `refs\u002Fheads\u002Fmain`. Feature branches can still add forbidden paths; the next\n   main merge re-introduces them. If your workflow merges feat → main, also\n   check on `refs\u002Fheads\u002Ffeat\u002F*` or enforce a server-side pre-receive rule.\n\n6. **GitHub's web UI keeps deleted files briefly.** After force-push, the\n   folder disappears from the tree view, but commits remain in reflog and\n   forks. To minimize: `gh api -X DELETE repos\u002F\u003Corg>\u002F\u003Crepo>\u002Fgit\u002Frefs\u002Fheads\u002F\u003Cleaked-branch>`\n   for any branches that ever pushed forbidden paths.\n\n7. **`mv + chmod 000` is reversible.** Don't rely on it alone. The pre-push\n   hook is the durable fix; the rename just stops the bleeding while you fix.\n\n8. **After fixing one cron, sibling landmines usually exist.** One bad launchd\n   job is rarely the only one in the fleet. Run\n   [`home-config-backup-audit`](..\u002Fhome-config-backup-audit\u002FSKILL.md) BEFORE\n   declaring the incident closed. Common siblings: dormant plists with the\n   same script, `auto-push-to-main.sh` invoked from cron pipelines, broken\n   scripts whose bugs are masking the leak.\n\n## Support files\n\n- [`references\u002Fgit-hooks-pre-push-stdin-format.md`](references\u002Fgit-hooks-pre-push-stdin-format.md) —\n  bug analysis of the field-order trap that makes hooks silently pass, with\n  3-test verification harness.\n- [`scripts\u002Fverify-hook-blocks-backup-push.sh`](scripts\u002Fverify-hook-blocks-backup-push.sh) —\n  re-runnable 3-test harness. Run BEFORE declaring any backup\u002F pre-push hook done.\n- [`templates\u002Fpre-push-backup-block.sh`](templates\u002Fpre-push-backup-block.sh) —\n  drop-in pre-push hook. Edit `FORBIDDEN_PATTERNS` + `PROTECTED_BRANCHES` env vars.\n- [`templates\u002Fbackup-leak-watchdog.sh`](templates\u002Fbackup-leak-watchdog.sh) —\n  re-emergence watchdog. Wrap in launchd plist (StartInterval 900).\n\n## Companion skill\n\n- [`home-config-backup-audit`](..\u002Fhome-config-backup-audit\u002FSKILL.md) —\n  **RUN THIS AFTER applying any fix from this skill.** Audits the entire fleet\n  (loaded launchd jobs + system crontab + dormant plists + scripts that combine\n  `git push` with `~\u002F` patterns + recurring-pipeline callers) for sibling\n  landmines (other crons\u002Fdormant scripts that would push to public repos if\n  re-activated). Use when user says \"double check all the backup jobs\" or\n  before declaring any single-fix event done.\n\n## Provenance\n\n- **Date:** 2026-07-15T20:00Z (first detected), fixed 2026-07-15T21:05Z\n- **Affected:** jleechanorg\u002Fclaude-commands public repo, ~491 MiB \u002F 6,820 files over 12 hours\n- **Trigger:** launchd `org.$USER.user-scope-backup` running `scripts\u002Fbackup-home.sh` every 7200s\n- **Files:** `~\u002F.claude\u002FCLAUDE.md`, `~\u002F.hermes\u002FCLAUDE.md`, `~\u002F.claude\u002Fagents\u002F*`, `~\u002F.claude\u002Fclaude-wa\u002Fbackups\u002F*`, `~\u002F.gemini\u002F*`, `~\u002F.opencode\u002Fstorage\u002Fsession_diff\u002F*`, `~\u002F.openclaw\u002F*`, `crontab.txt` — all under `backup\u002Fjeffreys-macbook-pro\u002F`\n- **Recurrence prevention:** `ai.hermes.backup-leak-watchdog` launchd plist (every 15 min) posts to `#all-$USER-ai` on any anomaly.\n- **Recipe memory:** See `~\u002F.hermes\u002FMEMORY.md` entry \"claude-commands backup\u002F leak (2026-07-15, 491 MiB PUBLIC)\" for the compressed replay.","# Backup-folder leak purge\n\nA launchd\u002Fcron job is running a home-config backup script that rsyncs `~\u002F` into\na repo's `backup\u002F` (or similar) folder then `git push origin main`. Pre-existing\n`.gitignore` entries are bypassed by `git add -f`. The gitleaks pre-push hook\ndoesn't catch this class because it scans for secret patterns, not path prefixes.\n\nThis skill gives the full 6-phase recipe. Run phases IN ORDER — the leak\nresumes within minutes if you skip ahead.\n\n## When to use this skill\n\n- A repo's `origin\u002Fmain` has paths like `backup\u002F`, `snapshot\u002F`, `home-config\u002F`,\n  `private\u002F`, `secrets\u002F` that you did NOT intentionally add.\n- A `scripts\u002Fbackup-home.sh` (or similar) is run by launchd and pushes to `origin\u002Fmain`.\n- A user says \"stop this backup cron\", \"the folder shouldn't be in this repo\",\n  \"my home dir is on github\", \"backup-home.sh is leaking\", or asks about a PR\n  with thousands of unrelated file changes.\n- `gh api repos\u002F\u003Corg>\u002F\u003Crepo>\u002Fgit\u002Ftrees\u002Fmain?recursive=1 --jq '[.tree[]|select(.path|startswith(\"backup\u002F\"))]|length'` returns a non-zero number.\n\n## Failure pattern\n\n1. A `scripts\u002Fbackup-home.sh` rsyncs a curated subset of `~\u002F` into `$REPO\u002Fbackup\u002F\u003Chostname>\u002F`.\n2. The script commits with `git add backup\u002F && git commit -m \"backup: automated home config snapshot ...\"`.\n3. If local branch is main, `git push origin main` directly. Otherwise a\n   temporary worktree is created on main, the commit is staged there, and pushed.\n4. `.gitignore` has `backup\u002F` but the script uses `git add -f backup\u002F` to bypass.\n5. The pre-push gitleaks hook scans staged content for secrets — `backup\u002F` has\n   no secret patterns, so the push is allowed.\n6. Each launchd tick (every 1-12h) adds a new \"backup: ...\" commit, growing the\n   leak by ~hundreds of MiB per day.\n\n## Recipe — 6 phases (execute in order)\n\n**CRITICAL: clarify user intent FIRST.** A home-config backup script often has TWO modes:\n- **Local mode** (writes to `$REPO_ROOT\u002Fbackup\u002F\u003Chost>\u002F` on disk, never pushes)\n- **Push mode** (also `git push`es to origin)\n\nIf the script supports local-only mode (via `ALLOW_GIT_BACKUP_PUSH=0`), the user may want\nthat preserved — only the push needs to be disabled. **Do NOT assume they want the entire\ncron killed.** Verified 2026-07-15 — Jeffrey said \"I want the files in user_scope\u002Fbackup,\nNOT in claude-commands backup\" after I overcorrected by disabling the whole cron.\n\n**Generalized rule (apply to ANY destructive fix):** When the user reports a bug\n\"X is happening that shouldn't\", default to:\n1. Identify the *minimal* intervention that stops X\n2. Preserve everything else that was working\n3. **Never** delete\u002Fdisable a wider system as the primary fix unless the user\n   explicitly says \"kill it\" \u002F \"stop it completely\" \u002F \"take it down\"\n4. If the user's request is ambiguous, prefer reversible targeted fixes\n   (env var change, config flip, plist reload) over irreversible ones\n   (rename + chmod 000, `mv .disabled`, git reset --hard, force-push)\n5. After the targeted fix, run the `home-config-backup-audit` skill to\n   verify NO sibling landmines exist before declaring done\n\n**Intent probe before Phase 1:**\n- \"Should the cron keep running and just stop pushing?\" → Skip Phase 1+2, do Phase 2-ALT instead\n- \"Stop the cron entirely?\" → All 6 phases\n\n### Phase 2-ALT: Push-only disable (most common — local backups wanted)\n\nWhen the cron should keep running but never push:\n\n```bash\n# In ~\u002FLibrary\u002FLaunchAgents\u002F\u003Clabel>.plist AND its .template source:\n#   \u003Ckey>ALLOW_GIT_BACKUP_PUSH\u003C\u002Fkey>\n#   \u003Cstring>0\u003C\u002Fstring>   # was 1\n#\n# Keep ALLOW_GIT_BACKUP_SYNC=1 so local backups still write to $REPO_ROOT\u002Fbackup\u002F\u003Chost>\u002F\n\n# Reload to pick up new env\nlaunchctl bootout gui\u002F$(id -u) ~\u002FLibrary\u002FLaunchAgents\u002F\u003Clabel>.plist\nlaunchctl load -w ~\u002FLibrary\u002FLaunchAgents\u002F\u003Clabel>.plist\n\n# Verify\nlaunchctl print gui\u002F$(id -u)\u002F\u003Clabel> | grep ALLOW_GIT_BACKUP_PUSH\n# Expected: ALLOW_GIT_BACKUP_PUSH => 0\n```\n\nThe next launchd tick runs the rsync + local write step (because `ALLOW_GIT_BACKUP_SYNC=1`)\nbut skips `git commit`\u002F`git push` (because `ALLOW_GIT_BACKUP_PUSH=0`). User gets local\nbackup files; nothing leaves the machine.\n\n### Phase 1: Stop the cron immediately\n\nThe cron runs every N hours. If you delete from origin first, the next tick\nre-pushes and you're back at square one.\n\n```bash\nlaunchctl list 2>\u002Fdev\u002Fnull | grep -E \"backup|home-config\" | head   # find label\nlaunchctl bootout gui\u002F$(id -u) ~\u002FLibrary\u002FLaunchAgents\u002F\u003Clabel>.plist\nmv ~\u002FLibrary\u002FLaunchAgents\u002F\u003Clabel>.plist ~\u002FLibrary\u002FLaunchAgents\u002F\u003Clabel>.plist.disabled-\u003Cdate>\n```\n\nVerify: `launchctl print gui\u002F$(id -u)\u002F\u003Clabel>` returns \"Could not find service\".\n\n### Phase 2: Kill the script itself\n\nDefense in depth — even if launchd is resurrected, the script can't run.\n\n```bash\nmv scripts\u002Fbackup-home.sh scripts\u002Fbackup-home.sh.DISABLED-\u003Cdate>-leak\nchmod 000 scripts\u002Fbackup-home.sh.DISABLED-\u003Cdate>-leak\nmv scripts\u002F\u003Corg>.\u003Clabel>.plist.template scripts\u002F\u003Corg>.\u003Clabel>.plist.template.DISABLED-\u003Cdate>\n```\n\n### Phase 3: Find the clean reset target + force-push origin\u002Fmain\n\n```bash\n# First commit that touched backup\u002F (chronologically earliest)\nFIRST_LEAK_SHA=$(gh api \"repos\u002F\u003Corg>\u002F\u003Crepo>\u002Fcommits?path=backup&per_page=100\" \\\n    --jq 'sort_by(.commit.author.date) | .[0].sha')\n\n# Parent of that commit = clean tip\nPARENT_SHA=$(gh api \"repos\u002F\u003Corg>\u002F\u003Crepo>\u002Fcommits\u002F$FIRST_LEAK_SHA\" --jq '.parents[0].sha')\n\n# Reset + force-push\ngit -C \u003Clocal-clone> reset --hard \"$PARENT_SHA\"\ngit -C \u003Clocal-clone> push --force-with-lease origin main\n```\n\n**Target selection:** Often the clean tip is the commit that originally added\n`backup\u002F` to `.gitignore`. Resetting there keeps the gitignore defense in place\nAND drops every leak commit. Don't reset to a \"good\" backup-fix commit — those\nadded plumbing that may also need to be reverted if it shipped to main.\n\n### Phase 4: Verify origin is clean\n\n```bash\ngh api \"repos\u002F\u003Corg>\u002F\u003Crepo>\u002Fgit\u002Ftrees\u002Fmain?recursive=1\" \\\n    --jq '[.tree[] | select(.path | startswith(\"backup\u002F\"))] | length'\n# Expected: 0\n\ngh api \"repos\u002F\u003Corg>\u002F\u003Crepo>\u002Fcontents\u002Fbackup\"\n# Expected: 404 Not Found\n```\n\n### Phase 5: Prevent recurrence\n\n**5a. Pre-push hook** — See [`templates\u002Fpre-push-backup-block.sh`](templates\u002Fpre-push-backup-block.sh).\nInstall at `\u003Crepo>\u002F.git\u002Fhooks\u002Fpre-push` (chmod 755) in EVERY clone that could\npush to this repo (especially the one the cron writes from).\n\n**Critical:** stdin field order per `git\u002Fgithooks.adoc` is\n`\u003Clocal-ref> SP \u003Clocal-sha> SP \u003Cremote-ref> SP \u003Cremote-sha>`. Wrong order\nmakes the hook silently pass. See\n[`references\u002Fgit-hooks-pre-push-stdin-format.md`](references\u002Fgit-hooks-pre-push-stdin-format.md)\nfor the full bug analysis + 3-test verification harness.\n\n**Verify the hook actually works** — see\n[`scripts\u002Fverify-hook-blocks-backup-push.sh`](scripts\u002Fverify-hook-blocks-backup-push.sh).\nRun this BEFORE declaring the fix done. If Test 1 (forbidden path in main\npush) exits 0 instead of 1, the hook is silently broken — fix immediately.\n\n**5b. Watchdog launchd job** — See [`templates\u002Fbackup-leak-watchdog.sh`](templates\u002Fbackup-leak-watchdog.sh).\nRuns every 15 min, checks: (a) `ALLOW_GIT_BACKUP_PUSH` in active plist is `0`\n(or plist absent — cron disabled), (b) origin\u002Fmain has 0 forbidden paths.\nPosts to Slack on any anomaly with 6h cooldown.\n\n**Note:** The earlier version of this watchdog checked \"is the launchd label\nunloaded\" — that's wrong if the cron is supposed to keep running locally.\nThe correct check is \"is `ALLOW_GIT_BACKUP_PUSH=1`\", since the cron being\nloaded is fine as long as it's not pushing.\n\n**5c. Token gate** — The existing gitleaks pre-push hook should ALSO refuse\nthe push if `git add -f backup\u002F` was used. Add the path-prefix check\n(BEFORE the secret scan) to your pre-push hook so both run.\n\n### Phase 6: Local cleanup\n\n```bash\ndu -sh ~\u002Fprojects_other\u002F\u003Cuser_scope>\u002Fbackup\u002F   # was 8.2 GiB in our case\nrm -rf ~\u002Fprojects_other\u002F\u003Cuser_scope>\u002Fbackup\u002F\n\n# Other dormant worktrees — leave the branch HEAD alone (history preserved),\n# but the working-dir residue is harmless if the branch is local-only and not pushed\n```\n\n## Pitfalls\n\n1. **Don't delete the .gitignore entry.** Even though the leak bypassed it via\n   `git add -f`, the gitignore prevents future accidental `git add backup\u002F`\n   from working in the common case.\n\n2. **Don't reset to the latest backup-fix commit.** Those commits modified\n   `scripts\u002Fbackup-home.sh` to make it dropbox-only by default — but they were\n   the same broken pipeline that leaked. Reset to the commit BEFORE the first\n   backup-touching commit (often the .gitignore commit itself).\n\n3. **Pre-push stdin field order is `\u003Clocal-ref> SP \u003Clocal-sha> ...`.** Common\n   bug: `read -r local_sha local_ref remote_sha _` swaps SHA and ref, hook\n   silently passes. See\n   [`references\u002Fgit-hooks-pre-push-stdin-format.md`](references\u002Fgit-hooks-pre-push-stdin-format.md).\n\n4. **gitleaks pre-push hooks don't catch path-prefix leaks.** They scan for\n   secret patterns (apiKey, tokens, etc.), not path prefixes. Add a path-prefix\n   check alongside the secret scan, not as a replacement.\n\n5. **The leak could happen in branches too.** The hook only enforces on\n   `refs\u002Fheads\u002Fmain`. Feature branches can still add forbidden paths; the next\n   main merge re-introduces them. If your workflow merges feat → main, also\n   check on `refs\u002Fheads\u002Ffeat\u002F*` or enforce a server-side pre-receive rule.\n\n6. **GitHub's web UI keeps deleted files briefly.** After force-push, the\n   folder disappears from the tree view, but commits remain in reflog and\n   forks. To minimize: `gh api -X DELETE repos\u002F\u003Corg>\u002F\u003Crepo>\u002Fgit\u002Frefs\u002Fheads\u002F\u003Cleaked-branch>`\n   for any branches that ever pushed forbidden paths.\n\n7. **`mv + chmod 000` is reversible.** Don't rely on it alone. The pre-push\n   hook is the durable fix; the rename just stops the bleeding while you fix.\n\n8. **After fixing one cron, sibling landmines usually exist.** One bad launchd\n   job is rarely the only one in the fleet. Run\n   [`home-config-backup-audit`](..\u002Fhome-config-backup-audit\u002FSKILL.md) BEFORE\n   declaring the incident closed. Common siblings: dormant plists with the\n   same script, `auto-push-to-main.sh` invoked from cron pipelines, broken\n   scripts whose bugs are masking the leak.\n\n## Support files\n\n- [`references\u002Fgit-hooks-pre-push-stdin-format.md`](references\u002Fgit-hooks-pre-push-stdin-format.md) —\n  bug analysis of the field-order trap that makes hooks silently pass, with\n  3-test verification harness.\n- [`scripts\u002Fverify-hook-blocks-backup-push.sh`](scripts\u002Fverify-hook-blocks-backup-push.sh) —\n  re-runnable 3-test harness. Run BEFORE declaring any backup\u002F pre-push hook done.\n- [`templates\u002Fpre-push-backup-block.sh`](templates\u002Fpre-push-backup-block.sh) —\n  drop-in pre-push hook. Edit `FORBIDDEN_PATTERNS` + `PROTECTED_BRANCHES` env vars.\n- [`templates\u002Fbackup-leak-watchdog.sh`](templates\u002Fbackup-leak-watchdog.sh) —\n  re-emergence watchdog. Wrap in launchd plist (StartInterval 900).\n\n## Companion skill\n\n- [`home-config-backup-audit`](..\u002Fhome-config-backup-audit\u002FSKILL.md) —\n  **RUN THIS AFTER applying any fix from this skill.** Audits the entire fleet\n  (loaded launchd jobs + system crontab + dormant plists + scripts that combine\n  `git push` with `~\u002F` patterns + recurring-pipeline callers) for sibling\n  landmines (other crons\u002Fdormant scripts that would push to public repos if\n  re-activated). Use when user says \"double check all the backup jobs\" or\n  before declaring any single-fix event done.\n\n## Provenance\n\n- **Date:** 2026-07-15T20:00Z (first detected), fixed 2026-07-15T21:05Z\n- **Affected:** jleechanorg\u002Fclaude-commands public repo, ~491 MiB \u002F 6,820 files over 12 hours\n- **Trigger:** launchd `org.$USER.user-scope-backup` running `scripts\u002Fbackup-home.sh` every 7200s\n- **Files:** `~\u002F.claude\u002FCLAUDE.md`, `~\u002F.hermes\u002FCLAUDE.md`, `~\u002F.claude\u002Fagents\u002F*`, `~\u002F.claude\u002Fclaude-wa\u002Fbackups\u002F*`, `~\u002F.gemini\u002F*`, `~\u002F.opencode\u002Fstorage\u002Fsession_diff\u002F*`, `~\u002F.openclaw\u002F*`, `crontab.txt` — all under `backup\u002Fjeffreys-macbook-pro\u002F`\n- **Recurrence prevention:** `ai.hermes.backup-leak-watchdog` launchd plist (every 15 min) posts to `#all-$USER-ai` on any anomaly.\n- **Recipe memory:** See `~\u002F.hermes\u002FMEMORY.md` entry \"claude-commands backup\u002F leak (2026-07-15, 491 MiB PUBLIC)\" for the compressed replay.",{"name":5,"tags":53,"description":7},[54,45,34,22,42,33,32,31],"security","42ccd02cbf5e8e47839dc3eab6fc754dc6b339cf","hermes\u002Fskills\u002Fbackup-folder-leak-purge",0,"https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills","https:\u002F\u002Fgithub.com\u002Fjleechanorg","2026-11-11 22:19:36",{"source":8,"skillName":5,"commands":62,"prompt":72,"zipAvailable":73},[63,66,69],{"tool":64,"command":65},"npx","npx skills add jleechanorg\u002Fjleechan-skills --skill backup-folder-leak-purge",{"tool":67,"command":68},"bunx","bunx skills add jleechanorg\u002Fjleechan-skills --skill backup-folder-leak-purge",{"tool":70,"command":71},"pnpm","pnpm dlx skills add jleechanorg\u002Fjleechan-skills --skill backup-folder-leak-purge","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills\u002Fblob\u002Fmain\u002Fhermes\u002Fskills\u002Fbackup-folder-leak-purge\u002FSKILL.md",true,[75,81,85,89,95,99,103,107],{"path":76,"resourceType":77,"resourceTypeDesc":78,"sizeBytes":79,"sourceHtmlUrl":80},"references\u002Fdetailed-guide.md","REFERENCE","参考文档",16542,"https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-leiloeiro-mercado\u002Freferences\u002Fdetailed-guide.md",{"path":82,"resourceType":77,"resourceTypeDesc":78,"sizeBytes":83,"sourceHtmlUrl":84},"references\u002Ffontes.md",400,"https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-leiloeiro-mercado\u002Freferences\u002Ffontes.md",{"path":86,"resourceType":77,"resourceTypeDesc":78,"sizeBytes":87,"sourceHtmlUrl":88},"references\u002Fgit-hooks-pre-push-stdin-format.md",4311,"https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills\u002Fblob\u002Fmain\u002Fhermes\u002Fskills\u002Fbackup-folder-leak-purge\u002Freferences\u002Fgit-hooks-pre-push-stdin-format.md",{"path":90,"resourceType":91,"resourceTypeDesc":92,"sizeBytes":93,"sourceHtmlUrl":94},"scripts\u002Fgovernance.py","SCRIPT","脚本",3690,"https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-leiloeiro-mercado\u002Fscripts\u002Fgovernance.py",{"path":96,"resourceType":91,"resourceTypeDesc":92,"sizeBytes":97,"sourceHtmlUrl":98},"scripts\u002Frequirements.txt",42,"https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-leiloeiro-mercado\u002Fscripts\u002Frequirements.txt",{"path":100,"resourceType":91,"resourceTypeDesc":92,"sizeBytes":101,"sourceHtmlUrl":102},"scripts\u002Fverify-hook-blocks-backup-push.sh",3603,"https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills\u002Fblob\u002Fmain\u002Fhermes\u002Fskills\u002Fbackup-folder-leak-purge\u002Fscripts\u002Fverify-hook-blocks-backup-push.sh",{"path":104,"resourceType":91,"resourceTypeDesc":92,"sizeBytes":105,"sourceHtmlUrl":106},"templates\u002Fbackup-leak-watchdog.sh",6270,"https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills\u002Fblob\u002Fmain\u002Fhermes\u002Fskills\u002Fbackup-folder-leak-purge\u002Ftemplates\u002Fbackup-leak-watchdog.sh",{"path":108,"resourceType":91,"resourceTypeDesc":92,"sizeBytes":109,"sourceHtmlUrl":110},"templates\u002Fpre-push-backup-block.sh",2929,"https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills\u002Fblob\u002Fmain\u002Fhermes\u002Fskills\u002Fbackup-folder-leak-purge\u002Ftemplates\u002Fpre-push-backup-block.sh",[112,129,151,165,179,195],{"skillNo":113,"name":114,"nameZh":6,"description":115,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":116,"categories":117,"keywords":118,"publishedAt":48},"01M716Y5MTJ6GYJZ2ATXCJK8G4","instinct","Use when the user invokes \u002Finstinct or \u002Fin, or asks to \"message Instinct\", \"ask Instinct\", \"tell Instinct\", \"check Instinct\", or read replies from the configured peer assistant. iMessage first, WhatsApp Web as fallback.","https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Finstinct\u002FSKILL.md",[],[114,119,24,120,121,122,123,124,125,126,127,128],"user","replies","check","use","identity","whatsapp","gmail","read","message","ask",{"skillNo":130,"name":131,"nameZh":6,"description":132,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":133,"categories":134,"keywords":140,"publishedAt":48},"01M716Y4HF7FVVMJSA2CCVN5VF","hr","Compatibility alias for history-resume; produce the same fresh-state, provenance-aware resume briefing.","https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fhr\u002FSKILL.md",[135],{"categoryNo":136,"categoryName":137,"taxonomyType":138,"taxonomyTypeDesc":139},"01JBSEEDCAT000000000000038","人力资源","PROFESSION","适用职业",[131,141,142,143,144,145,146,147,148,149,150],"resume","compatibility","provenance","for","history","produce","alias","briefing","state","fresh",{"skillNo":152,"name":153,"nameZh":6,"description":154,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":155,"categories":156,"keywords":158,"publishedAt":48},"01M716Y1FFNFKFJYYKF8HQA6T8","history-resume","Resume briefing for the current repository branch or worktree using fresh state and admitted history sources.","https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fhistory-resume\u002FSKILL.md",[157],{"categoryNo":136,"categoryName":137,"taxonomyType":138,"taxonomyTypeDesc":139},[145,141,159,160,119,161,148,162,144,163,164,149,150],"history resume","branch","analysis","repository","worktree","sources",{"skillNo":166,"name":167,"nameZh":6,"description":168,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":169,"categories":170,"keywords":171,"publishedAt":48},"01M716XWXJ8EPHK4RH9T14TR85","grok","Alias for the canonical grokbot browser conversation workflow; \u002Fgrok talks to the Grok bot the same way \u002Fdot talks to Dot.","https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fgrok\u002FSKILL.md",[],[167,172,173,174,144,175,147,176,177,178],"dot","bot","workflow","canonical","conversation","browser","grokbot",{"skillNo":180,"name":181,"nameZh":6,"description":182,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":183,"categories":184,"keywords":185,"publishedAt":48},"01M70N9Q07DTE0EW5KFXMMMSJ0","codexa-bench","Compare `codexa` and normal `codex` on a fair split of comparable work by default; use `--strict` for duplicate runs of the exact same task.","https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fcodexa-bench\u002FSKILL.md",[],[186,187,188,122,189,144,190,191,192,193,194],"codexa bench","work","split","default","duplicate","task","compare","normal","codex",{"skillNo":196,"name":197,"nameZh":6,"description":198,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":199,"licenseFileUrl":200,"sourceHtmlUrl":201,"categories":202,"keywords":214,"publishedAt":48},"01M6C24S2X60SP8BBFGAEKPEGW","frontend-design","Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML\u002FCSS layouts, or when styling\u002Fbeautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.","Apache-2.0","https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Ffrontend-design\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Ffrontend-design\u002FSKILL.md",[203,208,211],{"categoryNo":204,"categoryName":205,"taxonomyType":206,"taxonomyTypeDesc":207},"01JBSEEDCAT000000000000009","前端开发","SCENARIO","使用场景",{"categoryNo":209,"categoryName":210,"taxonomyType":206,"taxonomyTypeDesc":207},"01JBSEEDCAT000000000000015","视觉设计",{"categoryNo":212,"categoryName":213,"taxonomyType":138,"taxonomyTypeDesc":139},"01JBSEEDCAT000000000000032","设计师",[215,216,217,122,218,219,220,119,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,24,239],"frontend","design","frontend design","generic","ui design","react","html","components","code","artifacts","ui","aesthetics","Production","web components","landing","applications","quality","web","css","create","examples","with","react components","web ui","pages"]