Skip to content

GitHub Copilot CLI — Backup System Reference

Looking for a step-by-step restore runbook?

See the Disaster Recovery & Device Migration Guide for ordered phases and copy-paste commands.

This doc is the comprehensive reference — design rationale, complete inventory, decisions log, troubleshooting, and operational guidance.


30-second summary

  • What: Full automated backup of the Copilot CLI environment, working folder (C:\ssClawy\), and critical system configs.
  • Where: Personal OneDrive (primary) → mirrored to Personal Google Drive (secondary).
  • When: Twice daily (08:00 + 18:00 NZ time) via Windows Scheduled Task.
  • How big: ~6.3 GB cloud footprint (down from 91 GB pre-v3).
  • How long retained: Tier 1 keeps 14 most recent + 4 weekly snapshots = up to 18. Tiers 2/3/4 are live mirrors (one current copy each).
  • Privacy: customer-research-private/ is hard-excluded. All targets are Personal cloud (not Corp).
  • Restore time on a new device: ~30–45 minutes following the Disaster Recovery Guide.

Mental model — why it's called "tiered"

Backup is not one thing. Different content has different value, change rate, and size — so it deserves different treatment.

                    HIGH VALUE
           Versioned     │     Versioned
           (keep history)│     (keep history)
            ─── Tier 1 ──┼─────────────
            Live mirror  │     Live mirror
            (one current │     (one current
             copy)       │      copy)
                    ─── Tier 2/3/4 ──
                    LOW VALUE
              ─────  EXCLUDED (regenerable, transient, or sensitive)

The four tiers in plain English:

Tier "Mental category" Example Treatment
1 — Snapshots "Things that change often and where I might want to roll back" Custom instructions, MCP config, secrets, journal Timestamped snapshots with version history
2 — Session mirror "Append-only history I won't change but might want to read" Past Copilot CLI session folders Live mirror, single copy
3 — Working folder mirror "Big stuff I edit daily, mostly already in git" All of C:\ssClawy\ (planets, sites, tools) Live mirror, single copy, build artifacts excluded
4 — System mirror "Stuff that's tied to this machine but not Copilot-specific" Azure CLI auth, OBS profile, Pictures Live mirror, single copy
— Excluded "Don't waste storage on these" node_modules/, events.jsonl, logs/, build outputs Never copied

Why versioning for Tier 1 only? Because that's where you want history (e.g. "what was my voice guardrail two weeks ago?", "which version of mcp-config.json worked?"). For Tiers 2/3/4, history is either preserved by git (Tier 3) or not actually useful (you never want to "roll back" your Pictures folder).


Architecture overview

┌──────────────────────────────────────────────────────────────┐
│  YOUR PC                                                      │
│                                                               │
│  ~/.copilot/             → Tier 1 (snapshots) + Tier 2        │
│      ├── copilot-instructions*.md                             │
│      ├── secrets/, skills/, mcp-config.json, *.md             │
│      ├── session-store.db                                     │
│      ├── session-state/  → Tier 2 (mirror, no events.jsonl)   │
│      └── (excluded: logs/, mcp-servers/, crash-reports/, …)   │
│                                                               │
│  C:\ssClawy\             → Tier 3 (mirror)                    │
│      ├── clawpilot/, shift/, claw-planet/, agentic-planet/    │
│      ├── plainai/, cosmos-atlas/, agent-academy/, guided/     │
│      ├── learning-docs/, ainews/, m365-roadmap/               │
│      ├── connect-fy26/, connect-tracking/                     │
│      ├── z4-screenshots/, agent365-deck-unpacked/, …          │
│      └── (excluded: node_modules/, dist/, customer-private/)  │
│                                                               │
│  Other system data       → Tier 1 system-configs/ (small)     │
│      ├── ~/.gitconfig, ~/.npmrc                               │
│      ├── %APPDATA%/GitHub CLI/                                │
│      ├── %APPDATA%/Code/User/  (settings/keybindings/snippets)│
│      ├── C:/Windows/.../hosts                                 │
│      ├── PSReadLine history                                   │
│      └── Scheduled Task XML export                            │
│                                                               │
│  Other system data       → Tier 4 (system-mirror)             │
│      ├── ~/.azure/         (Azure CLI auth)                   │
│      ├── %APPDATA%/obs-studio/  (YouTube recording)           │
│      └── ~/Pictures/                                          │
└────────┬─────────────────────────────────────────────────────┘
         │  Twice daily 08:00 + 18:00 NZ
         │  (Windows Scheduled Task: CopilotCLI_BackupInstructions)
┌──────────────────────────────────────────────────────────────┐
│  PRIMARY:  Personal OneDrive                                  │
│  ~/OneDrive/CopilotCLI_Backups/                               │
│      ├── snapshots/                                           │
│      │   ├── snapshot_2026-05-09_0820/  (~50 MB)              │
│      │   ├── snapshot_2026-05-09_1800/                        │
│      │   └── … (last 14 + 4 weekly = up to 18)                │
│      ├── session-mirror/         (Tier 2, one live copy)      │
│      ├── ssclawy-mirror/         (Tier 3, one live copy)      │
│      └── system-mirror/          (Tier 4, one live copy)      │
└────────┬─────────────────────────────────────────────────────┘
         │  robocopy /MIR (last step of every run)
┌──────────────────────────────────────────────────────────────┐
│  SECONDARY: Personal Google Drive                             │
│  G:\My Drive\CopilotCLI_Backups\                              │
│      └── (exact mirror of Personal OneDrive)                  │
└──────────────────────────────────────────────────────────────┘

Complete inventory — what gets backed up

Tier 1 — Snapshots (versioned, timestamped, ~50 MB each)

Each snapshot_YYYY-MM-DD_HHmm/ folder is a complete point-in-time copy of:

Copilot CLI core

Item Source Why it matters
Custom instructions ~/.copilot/copilot-instructions.md Your CLI's voice, rules, behaviour
Reference doc ~/.copilot/copilot-instructions-reference.md Project context, deploy commands, tool registry
Session journal + archive ~/.copilot/session-journal*.md Continuity across sessions
Plain AI / curriculum / voice guardrails ~/.copilot/plain-ai-*.md Brand voice rules
Connect tracking framework ~/.copilot/connect-*.md (if present) Performance review evidence framework
MCP config ~/.copilot/mcp-config.json MCP server definitions (incl. Entra creds)
Secrets ~/.copilot/secrets/ GitHub PAT, other credentials
Skills ~/.copilot/skills/ Custom CLI skills
Encryption keys ~/.copilot/m-encryption-key.enc, m-github-update-token.enc Required to decrypt other config
Session search index ~/.copilot/session-store.db Searchable history of all sessions
Tool versions manifest generated environment-manifest.json Tool versions, npm globals, pip packages, repo list
Windows Terminal settings %LOCALAPPDATA%/.../WindowsTerminal/.../settings.json Custom profiles
YouTube MCP creds %APPDATA%/npm/.../youtube-channel-mcp/{credentials,tokens}.json OAuth for YouTube Channel MCP

System configs (added in v3, lives in system-configs/ subfolder)

Item Source Why it matters
Global git config ~/.gitconfig git user identity (name, email, defaults)
Global gitignore ~/.gitignore_global (if exists) Personal exclude patterns
npm config ~/.npmrc npm registry tokens
GitHub CLI auth %APPDATA%/GitHub CLI/ (folder copy) gh PAT cache
VS Code User settings %APPDATA%/Code/User/{settings,keybindings,tasks,argv}.json Editor preferences
VS Code User snippets %APPDATA%/Code/User/snippets/ Custom snippets
VS Code extensions list generated vscode-extensions.txt One-shot reinstall on new device
Hosts file C:/Windows/System32/drivers/etc/hosts Custom network mappings
PSReadLine history %APPDATA%/Microsoft/Windows/PowerShell/PSReadLine/ConsoleHost_history.txt Terminal up-arrow muscle memory
Scheduled task XML exported CopilotCLI_BackupInstructions.xml Recreate the backup task on new device

Tier 2 — Session-mirror (live, single copy, ~400 MB)

  • ~/.copilot/session-state/ — every Copilot CLI session folder (currently 584+ folders)
  • Excludes events.jsonl files — these are runtime telemetry (~3.7 GB if included). Removing them shrinks the mirror by 90% with no loss of restorable state.

Tier 3 — ssclawy-mirror (live, single copy, ~5.7 GB)

  • The full C:\ssClawy\ working folder
  • Includes all loose root files (starter prompts, Python scripts, screenshots)
  • Includes all .git/ folders inside repos — preserves uncommitted work, stashes, reflog
  • Excludes build artifacts at any nesting depth (see Exclusions below)
  • 🛡️ Excludes customer-research-private/ (privacy guardrail)

Tier 4 — system-mirror (live, single copy, ~155 MB)

Item Source Why it matters
Azure CLI ~/.azure/ (full folder) Subscription/profile data, auth context
OBS Studio %APPDATA%/obs-studio/ YouTube recording profiles, scenes, sources
Pictures ~/Pictures/ Personal photos folder

What is NOT backed up

Different reasons for different exclusions. Knowing why something is excluded is as important as knowing what is excluded — it prevents future "should I add X?" debates.

Excluded — regenerable

Item Where Why excluded
~/.copilot/logs/ Local diagnostic logs (~24 GB) Regenerated automatically; only useful for live debugging
~/.copilot/mcp-servers/ npm packages Reinstalled via npm install per server
~/.copilot/installed-plugins/ Plugin metadata Regenerated on first plugin install
~/.copilot/bin/, ide/, heartbeat/ Runtime/transient Recreated when CLI starts
~/.copilot/session-store.db-shm, .db-wal SQLite runtime temp files Regenerated by SQLite

Excluded — runtime telemetry, not state

Item Where Why excluded
events.jsonl files Inside ~/.copilot/session-state/<id>/ ~3.7 GB of session telemetry. Restoring without them costs nothing — the session JSON files have all resumable state.
~/.copilot/crash-reports/ Crash dumps Useful only at the moment of a crash; stale after that
~/.copilot/m-playwright-profiles/ Browser profiles Regenerated on first Playwright run
~/.copilot/m-sessions/ Playwright runtime Regenerated

Excluded — build artifacts (Tier 3)

These get excluded by robocopy /XD at any nesting depth:

Pattern Used by Recreated by
node_modules/ npm npm install
.next/ Next.js npm run build
.astro/ Astro npm run build
.svelte-kit/ SvelteKit npm run build
.turbo/ Turborepo turbo build
.nuxt/ Nuxt npm run build
.angular/ Angular ng build
dist/, build/, out/ Generic Project-specific build
_site/ Jekyll jekyll build
public/_pagefind/ Astro Pagefind Generated during build
resources/_gen/ Hugo Generated during build
.venv/, venv/ Python python -m venv
__pycache__/, .pytest_cache/ Python Regenerated automatically
bin/, obj/ .NET Build
target/ Rust cargo build
.gradle/ Gradle Build
.cache/, .parcel-cache/ Generic Regenerated
coverage/ Test coverage tools Re-run tests
.vscode-test/, .idea/ IDEs Regenerated
.terraform/ Terraform terraform init

Excluded — privacy guardrail

Item Why excluded
C:\ssClawy\customer-research-private/ 🛡️ Microsoft customer-confidential data must NOT go to Personal cloud. If you store research that lives there, keep it there.

Excluded — too large or rarely permanent

Item Why excluded
~/Downloads/ (5.8 GB) Mostly transient files; rarely contains permanent assets
~/Desktop/, ~/Documents/ Empty for Sush; not worth carrying any growth budget

Privacy & security

Why Personal cloud, not Corp cloud

We originally backed up to Corp OneDrive (OneDrive - Microsoft). On 2026-04-04, Microsoft Purview DLP scans flagged mcp-config.json for containing Microsoft Entra Client Secrets — Corp cloud is monitored for sensitive data. Moved to Personal OneDrive to keep full backups including secrets, mirrored to Personal Google Drive.

What's in your backup that's sensitive

The backup contains:

  • GitHub PAT (~/.copilot/secrets/github-personal-pat) — gives access to all your repos
  • Microsoft Entra client secret (in mcp-config.json) — auth for MCP servers
  • YouTube OAuth refresh tokens (youtube-mcp-creds/tokens.json) — full access to your YouTube channel
  • GitHub CLI auth (%APPDATA%/GitHub CLI/) — same auth as gh CLI
  • npm registry tokens (~/.npmrc) — auth for private npm packages, if any
  • Azure CLI tokens (~/.azure/) — Azure subscription auth (note: tokens are DPAPI-encrypted to your Windows account, so they don't transfer to a new device — re-auth needed)

Privacy guardrail

C:\ssClawy\customer-research-private/ is explicitly excluded by the script. The exclude pattern is in $tier3ExcludeDirs. DO NOT rename that folder, move customer data into another folder under C:\ssClawy\, or remove the exclusion. If you ever need to back up customer research, do it via Corp OneDrive (which has DLP).

Defence in depth

Personal cloud backup is one layer. For extra safety:

  • Store GitHub PAT, Entra client secrets, and other tokens in 1Password / Bitwarden as a parallel layer (cloud-account-independent).
  • Keep MFA backup codes off the laptop entirely (printed on paper or in a password manager).
  • Don't enable plaintext credentials anywhere outside ~/.copilot/secrets/ and mcp-config.json.

The "what if I lose both clouds" question

Practically: if you simultaneously lose access to your Microsoft account AND your Google account, this backup is gone. Mitigation: your password manager (1Password etc.) is independent of both. Use it as the source of truth for tokens that absolutely must survive.


The script — annotated

Lives at C:\Users\ssutheesh\.copilot\backup-instructions.ps1. Key design choices:

1. Helper: Invoke-WithTimeout

function Invoke-WithTimeout {
    param([scriptblock]$ScriptBlock, [int]$TimeoutSeconds = 30, $Default = '')
    $job = Start-Job -ScriptBlock $ScriptBlock
    if (Wait-Job $job -Timeout $TimeoutSeconds) {
        $result = Receive-Job $job
        Remove-Job $job
        return $result
    } else {
        Stop-Job $job -ErrorAction SilentlyContinue
        Remove-Job $job -Force -ErrorAction SilentlyContinue
        return $Default
    }
}

Why: v1 hung forever when npm list -g or pip list stalled. Every external command is now wrapped — if it doesn't return in 30 seconds, we use a default value and continue.

2. Tier 1 — exclude-list, not include-list

$tier1ExcludeDirs = @(
    'session-state', 'logs', 'mcp-servers',
    'backups', 'restart',
    'crash-reports', 'm-playwright-profiles', 'm-sessions',
    'installed-plugins', 'bin', 'ide', 'heartbeat'
)
Get-ChildItem $copilotDir -Directory | Where-Object {
    $_.Name -notin $tier1ExcludeDirs
} | ForEach-Object { Copy-Item $_.FullName "$snapFolder\$($_.Name)" -Recurse -Force }

Why exclude-list? If Copilot CLI adds a new config folder later (say ~/.copilot/agents/), an exclude-list silently includes it (the safe default). An include-list would silently miss it (data loss). Future-safe by default.

3. Retention — 14 most recent + 4 weekly

$allSnaps = Get-ChildItem "$snapDir\snapshot_*" -Directory | Sort-Object Name -Descending
$keep = New-Object 'System.Collections.Generic.HashSet[string]'

# Last 14 (= 7 days × 2/day)
$allSnaps | Select-Object -First 14 | ForEach-Object { [void]$keep.Add($_.Name) }

# 4 weekly: walk older snapshots, take the first one we see in each new ISO week
$weeksKept = @{}
$allSnaps | Select-Object -Skip 14 | ForEach-Object {
    if ($weeksKept.Count -ge 4) { return }
    if ($_.Name -match 'snapshot_(\d{4})-(\d{2})-(\d{2})') {
        $d = [datetime]::ParseExact("$($matches[1])-$($matches[2])-$($matches[3])", 'yyyy-MM-dd', $null)
        $weekKey = "{0}-W{1:D2}" -f $d.Year, [int][System.Globalization.ISOWeek]::GetWeekOfYear($d)
        if (-not $weeksKept.ContainsKey($weekKey)) {
            $weeksKept[$weekKey] = $true
            [void]$keep.Add($_.Name)
        }
    }
}

$allSnaps | Where-Object { -not $keep.Contains($_.Name) } | Remove-Item -Recurse -Force

Why this shape? "Last 14" gives same-day and previous-day rollback for most accidental edits. "4 weekly" extends history to ~5 weeks of recovery options without storing every snapshot. Weekly snapshots are picked using ISO 8601 week numbering (Mon–Sun) so they're stable across timezones.

4. Tier 2 — robocopy /MIR /XF events.jsonl

robocopy $sessionSrc $sessionMir /MIR /XF events.jsonl /R:2 /W:1 /NJH /NJS /NP /NFL /NDL

Why /XF not /XD? /XF excludes specific file names. events.jsonl lives at <session-id>/events.jsonl, not in a dedicated subdir, so we can't exclude it by directory name. /XF events.jsonl skips every file with that name anywhere in the tree.

5. Tier 3 — robocopy /MIR /XD <list>

$tier3ExcludeDirs = @('node_modules', '.next', '.astro', ..., 'customer-research-private')
$xdArgs = @()
foreach ($d in $tier3ExcludeDirs) { $xdArgs += '/XD'; $xdArgs += $d }
& robocopy $ssClawyDir $ssClawyMir /MIR /XJ /R:1 /W:1 /NJH /NJS /NP /NFL /NDL @xdArgs

Why /XD matches by name at any depth? Robocopy's /XD excludes any directory whose name matches, regardless of nesting. So /XD node_modules excludes clawpilot/node_modules, shift/node_modules, shift/foo/bar/node_modules — all of them. This is exactly what we want.

Why /XJ? Skip junction points and reparse points. Without it, robocopy could follow a symlink loop or backup OS-internal junctions that aren't real data.

6. Google Drive last, with /XO

robocopy $backupRoot $gdBackup /MIR /XJ /XO /R:1 /W:1 /NJH /NJS /NP /NFL /NDL

Why last? Google Drive streams files (slow). If GD hangs, we want all local OneDrive work to be already done.

Why /XO? "Exclude older" — only copy files newer than what's on GD. With /MIR, this means: incremental safe re-runs after a partial failure (won't re-upload files that already finished).


The Scheduled Task

View the current task

Get-ScheduledTask -TaskName 'CopilotCLI_BackupInstructions' | Select TaskName, State
Get-ScheduledTaskInfo -TaskName 'CopilotCLI_BackupInstructions' |
    Select LastRunTime, LastTaskResult, NextRunTime

LastTaskResult = 0 means success. Code 267009 means "task is currently running" (also OK, not an error).

Manual run (e.g. before a risky operation)

& "$env:USERPROFILE\.copilot\backup-instructions.ps1"

Recreate from scratch (if task was deleted)

The XML is in every Tier 1 snapshot. Restore with:

$latest = Get-ChildItem "$env:USERPROFILE\OneDrive\CopilotCLI_Backups\snapshots\snapshot_*" -Directory |
    Sort-Object Name -Descending | Select-Object -First 1
$xml = Get-Content "$($latest.FullName)\system-configs\scheduled-tasks\CopilotCLI_BackupInstructions.xml" -Raw
Register-ScheduledTask -TaskName 'CopilotCLI_BackupInstructions' -Xml $xml -User "$env:USERNAME"

Or recreate manually (Admin PowerShell):

$action = New-ScheduledTaskAction `
    -Execute "pwsh.exe" `
    -Argument '-NoProfile -WindowStyle Hidden -File "C:\Users\ssutheesh\.copilot\backup-instructions.ps1"'

$trigger1 = New-ScheduledTaskTrigger -Daily -At "08:00"
$trigger2 = New-ScheduledTaskTrigger -Daily -At "18:00"

$settings = New-ScheduledTaskSettingsSet `
    -AllowStartIfOnBatteries `
    -DontStopIfGoingOnBatteries `
    -StartWhenAvailable

Register-ScheduledTask `
    -TaskName "CopilotCLI_BackupInstructions" `
    -Action $action `
    -Trigger @($trigger1, $trigger2) `
    -Settings $settings `
    -Description "Twice-daily backup of Copilot CLI + working folder to Personal OneDrive (mirrored to Google Drive)"

Verification

Quick health check (every session start)

$root = "$env:USERPROFILE\OneDrive\CopilotCLI_Backups"

# 1. Total size — should be ~6 GB, NOT 91 GB
'{0:N2} GB' -f ((Get-ChildItem $root -Recurse -File -Force | Measure-Object Length -Sum).Sum / 1GB)

# 2. Tier folders all present
@('snapshots', 'session-mirror', 'ssclawy-mirror', 'system-mirror') |
    ForEach-Object { "$_ : $(Test-Path "$root\$_")" }

# 3. Latest snapshot recent (within last 12h)
$latest = Get-ChildItem "$root\snapshots\snapshot_*" -Directory |
    Sort-Object Name -Descending | Select-Object -First 1
$age = (Get-Date) - $latest.LastWriteTime
"Latest snapshot: $($latest.Name) ($([int]$age.TotalHours)h old)"

Deep verification (monthly)

# Privacy guardrail: customer-research-private must NOT exist in mirror
Test-Path "$root\ssclawy-mirror\customer-research-private"   # → False ✓

# session-mirror should have ZERO events.jsonl files
(Get-ChildItem "$root\session-mirror" -Recurse -Filter 'events.jsonl' -Force).Count   # → 0 ✓

# ssclawy-mirror should have ZERO node_modules folders at any depth
(Get-ChildItem "$root\ssclawy-mirror" -Recurse -Directory -Filter 'node_modules' -Force).Count   # → 0 ✓

# Critical content present
@('connect-fy26', 'connect-tracking', 'cosmos-atlas', 'aguidetocloud-revamp', 'plainai',
  'agent365-deck-unpacked', 'z4-screenshots', 'learning-docs') | ForEach-Object {
    "$_ : $(Test-Path "$root\ssclawy-mirror\$_")"
}

# System configs in latest snapshot
$latest = Get-ChildItem "$root\snapshots\snapshot_*" -Directory |
    Sort-Object Name -Descending | Select-Object -First 1
@('.gitconfig', '.npmrc', 'github-cli', 'vscode-user', 'windows-hosts',
  'psreadline-history.txt', 'scheduled-tasks') | ForEach-Object {
    "system-configs/$_ : $(Test-Path "$($latest.FullName)\system-configs\$_")"
}

# Snapshot count is within retention (≤ 18)
(Get-ChildItem "$root\snapshots\snapshot_*" -Directory).Count   # → ≤ 18

# Google Drive matches OneDrive size (within 100 MB tolerance for sync lag)
$gdSize = (Get-ChildItem 'G:\My Drive\CopilotCLI_Backups' -Recurse -File -Force |
    Measure-Object Length -Sum).Sum / 1GB
$odSize = (Get-ChildItem $root -Recurse -File -Force |
    Measure-Object Length -Sum).Sum / 1GB
'OD: {0:N2} GB | GD: {1:N2} GB | Δ: {2:N3} GB' -f $odSize, $gdSize, [math]::Abs($odSize - $gdSize)

Restore scenarios

Scenario A — New Windows laptop (most common)

Follow the Disaster Recovery Guide. Estimated time: 30–45 minutes.

Key sequence:

  1. Install Google Drive for Desktop → sign in → mounts as G:\
  2. Install base tools (git, node, python, pwsh, gh, az, hugo) via winget
  3. Restore ~/.copilot/ from latest snapshot
  4. Restore system configs (gitconfig, npmrc, gh CLI, VS Code, hosts, PSReadLine) from system-configs/
  5. Reinstall VS Code extensions from vscode-extensions.txt
  6. Restore session history from session-mirror/
  7. Restore C:\ssClawy\ from ssclawy-mirror/
  8. Restore Azure CLI from system-mirror/azure-cli/
  9. Re-register the Scheduled Task from XML
  10. npm install in each project that needs it
  11. az login to refresh DPAPI-encrypted Azure tokens

Scenario B — Mac

Most things are portable. Differences:

  • Paths: ~/.copilot/ and ~/.azure/ translate directly. Use ~/Library/Application Support/Code/User/ for VS Code on Mac.
  • Hosts file is at /etc/hosts on Mac (need sudo).
  • No PowerShell scheduled task — use cron or launchd. Sample launchd plist:
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.sush.copilotcli.backup</string>
  <key>ProgramArguments</key>
  <array>
    <string>/usr/local/bin/pwsh</string>
    <string>-NoProfile</string>
    <string>-File</string>
    <string>/Users/sush/.copilot/backup-instructions.ps1</string>
  </array>
  <key>StartCalendarInterval</key>
  <array>
    <dict><key>Hour</key><integer>8</integer><key>Minute</key><integer>0</integer></dict>
    <dict><key>Hour</key><integer>18</integer><key>Minute</key><integer>0</integer></dict>
  </array>
  <key>RunAtLoad</key>
  <false/>
</dict>
</plist>

Save to ~/Library/LaunchAgents/com.sush.copilotcli.backup.plist, then launchctl load ~/Library/LaunchAgents/com.sush.copilotcli.backup.plist.

  • The script itself needs minor tweaks: PowerShell on Mac doesn't have Export-ScheduledTask or Get-ScheduledTask; remove those calls or guard with if ($IsWindows). Robocopy isn't available — use rsync -a --delete --exclude=... instead.
  • Google Drive for Desktop on Mac mounts under ~/Google Drive/My Drive/, not G:\. Update the $gdBackup path.

Scenario C — Cloud devbox (Codespaces, Azure VM, AWS Cloud9)

Different shape — you don't need the full C:\ssClawy\ mirror because the devbox can re-clone repos from GitHub.

Minimal restore:

  1. Sign into GitHub
  2. Read environment-manifest.json from your latest snapshot:
    # in a Codespace, after pulling the snapshot from your GD/OneDrive
    cat snapshot/environment-manifest.json | jq '.repos[]'
    
  3. git clone only the repos you need (Codespaces is typically a single-repo environment)
  4. Copy the relevant config files from system-configs/ into the devbox
  5. npm install in the project

This is faster than a full Windows restore because no large files need to be transferred — git pulls do the heavy lifting.


Common issues & troubleshooting

"Backup task ran but no new snapshot appeared"

Check the task's last result:

Get-ScheduledTaskInfo -TaskName 'CopilotCLI_BackupInstructions' |
    Select LastRunTime, LastTaskResult
Result Meaning
0 Success
267009 Task currently running (also OK)
1 Generic failure — run script manually to see error
267011 Task has not yet run
2147942401 The system cannot find the file specified — script path wrong

Run the script manually to see the actual error:

& "$env:USERPROFILE\.copilot\backup-instructions.ps1"

"Snapshot grew suddenly from ~50 MB to ~500 MB"

Something new ended up under ~/.copilot/ and it's not on the exclude list. Find it:

$latest = Get-ChildItem "$env:USERPROFILE\OneDrive\CopilotCLI_Backups\snapshots\snapshot_*" -Directory |
    Sort-Object Name -Descending | Select-Object -First 1
Get-ChildItem $latest.FullName -Force | ForEach-Object {
    if ($_.PSIsContainer) {
        $sz = (Get-ChildItem $_.FullName -Recurse -File -Force | Measure-Object Length -Sum).Sum / 1MB
        '{0,8:N1} MB  {1}' -f $sz, $_.Name
    }
} | Sort-Object -Descending

If it's a new "data" folder (e.g. cache), add to $tier1ExcludeDirs in the script. If it's legit config, leave it.

"Google Drive is missing files compared to OneDrive"

Cause: GD mirror partially completed or hung. Run mirror manually:

robocopy "$env:USERPROFILE\OneDrive\CopilotCLI_Backups" "G:\My Drive\CopilotCLI_Backups" `
    /MIR /XJ /R:1 /W:1 /NJH /NJS /NP /NFL /NDL

If GD is stuck waiting for sync: open Google Drive desktop app, check status icon. May need to pause + resume sync.

"session-mirror has events.jsonl files in it"

Should be 0. If not, the script's /XF events.jsonl is being ignored or the source has unusual file names. Check:

Get-ChildItem "$env:USERPROFILE\OneDrive\CopilotCLI_Backups\session-mirror" `
    -Recurse -Filter 'events.jsonl' -Force | Select-Object FullName

If found, manually delete and re-run the script — /MIR doesn't retroactively remove files that were once present without the exclude rule.

"ssclawy-mirror has node_modules in it"

Same shape as above — somewhere a folder named node_modules is being captured. Find and delete:

Get-ChildItem "$env:USERPROFILE\OneDrive\CopilotCLI_Backups\ssclawy-mirror" `
    -Recurse -Directory -Filter 'node_modules' -Force |
    Remove-Item -Recurse -Force

Then re-run the script. Investigate why it appeared (could be a junction, a renamed folder, etc.).

"OneDrive is rejecting my secrets file"

If you're on a Corp OneDrive (you shouldn't be — this script targets ~/OneDrive/, the personal one): Microsoft Purview DLP scans for client secrets. Move your OneDrive root to Personal account. Verify your ~/OneDrive/ is the personal one:

(Get-Item $env:OneDrive).FullName
# Expected: C:\Users\<you>\OneDrive   (personal)
# NOT:      C:\Users\<you>\OneDrive - Microsoft   (corp)

"I'm running out of Google Drive space"

Tier sizes should add up to ~6.3 GB. Larger means something has slipped past an exclusion. Check:

$gd = "G:\My Drive\CopilotCLI_Backups"
Get-ChildItem $gd -Directory | ForEach-Object {
    $sz = (Get-ChildItem $_.FullName -Recurse -File -Force | Measure-Object Length -Sum).Sum / 1GB
    '{0,8:N2} GB  {1}' -f $sz, $_.Name
}

If ssclawy-mirror is much bigger than ~6 GB, something new is leaking through. Common culprits: a new build folder pattern not yet on the exclude list (e.g. a new framework's cache dir), or a one-off large folder Sush dropped under C:\ssClawy\.


Performance & sizing

Current footprint (as of 2026-05-09)

Tier Size
1 — snapshots/ (3 active, will grow to ~18) ~150 MB → ~900 MB at steady state
2 — session-mirror/ 377 MB
3 — ssclawy-mirror/ 5,743 MB
4 — system-mirror/ 155 MB
Total ~6.3 GB

Growth model

  • Tier 1: stable. Each snapshot ≈ 50 MB. Adding new system configs would grow this by ~MBs per snapshot.
  • Tier 2: grows ~5–20 MB per session you run (a typical Copilot CLI session creates 1–10 MB of state.json etc, excluding events). At 10 sessions/day, ~50 MB/week.
  • Tier 3: grows with C:\ssClawy\ content. New planet/tool repos add to this. Expect ~50–200 MB/month.
  • Tier 4: stable. Azure CLI cache fluctuates. OBS profile is fixed. Pictures grows slowly.

When to revisit the design

If total cloud footprint grows past 10 GB, revisit:

  • Is something slipping past Tier 3 exclusions?
  • Should events.jsonl exclusion be re-confirmed (Tier 2 should stay <600 MB)?
  • Is a new framework using a cache dir we haven't added to $tier3ExcludeDirs?
  • Should we add a periodic "trim" — e.g. delete session-mirror/ folders older than 6 months?

If at 15 GB (Personal Google Drive free tier limit), urgent: investigate or upgrade GD plan.


Design history & decisions

v1 (2026-03-27 to 2026-05-08)

Design: Daily 9 AM single-tier snapshot — every config folder under ~/.copilot/ got copied into a timestamped folder. 30 retained. Plus a separate learning-docs-backup folder.

Why it failed:

  1. session-state/ was included, with all events.jsonl runtime telemetry. ~4 GB per snapshot.
  2. 30 snapshots × 4 GB = 120 GB ceiling, actual usage hit 91.79 GB.
  3. Twice-daily was promoted from daily somewhere along the way without updating the doc — so retention dropped from 30 days to 15 days.
  4. External commands hung the script (npm list -g, pip list) when their output was slow.

v3 (2026-05-09, current)

Why "v3" and not "v2"? v2 was the iteration where we added Personal OneDrive (2026-04-04). The scope-and-size redesign is large enough to warrant its own version label.

Design rationale:

  1. Tiered model. Different content has different value; treating all of it the same was the v1 mistake. Versioning history is valuable for things that change AND can be corrupted (config). It's wasteful for things that are append-only (session history) or already versioned by git (working folder).
  2. Excluded events.jsonl. Saved 3.7 GB per session-mirror cycle. Confirmed via a sample audit: restoring without events.jsonl produces a fully usable session (the runtime telemetry isn't restorable state).
  3. Added Tier 3 (C:\ssClawy\ mirror). Sush wanted disaster recovery on a new device to be 30 minutes, not 6 hours of re-cloning 30 repos. The mirror covers planets, sites, screenshots, slides, Connect tracking — everything that's not in git or that has uncommitted work.
  4. Build-artifact exclusions in Tier 3. Without exclusions, C:\ssClawy\ is 9.9 GB. With them, 5.7 GB. The 4.2 GB saved is node_modules + dist + .astro + bin — all reproducible from package.json etc.
  5. Privacy guardrail. customer-research-private/ is hard-excluded in code. The exclude list also serves as documentation: anyone reading the script understands the privacy contract.
  6. Added Tier 4 system configs. v1 missed a lot of "obvious in hindsight" things: git config, gh CLI auth, VS Code extensions, hosts file. v3 covers all the disaster-recovery basics on a new device.
  7. Retention 14+4 weekly instead of 30 raw. 30 raw snapshots at twice-daily = 15 days of history. 14+4 weekly = 7 days dense + ~4 weeks sparse = ~5 weeks of recovery options. Better shape for the same storage budget.
  8. Timeouts on every external command. v1 hung; v3 can't.

Decisions made during the v3 rubber-duck session

Decision Choice Rationale
Frequency Twice daily (kept) Sush explicitly chose this — comfort of "if I damage something AM, the PM run will roll forward but yesterday's PM is still in retention"
Snapshot retention 14 most recent + 4 weekly Math of twice-daily × 7 days = 14 snapshots covers most rollback needs; 4 weekly extends to ~5 weeks
Include .git folders in Tier 3 mirror Yes (+1.3 GB) Preserves uncommitted work, stashes, reflog. Without this, restoring would lose any WIP not yet committed.
Include customer-research-private/ No (hard exclude) Microsoft customer data must not go to Personal cloud. Privacy >> backup completeness.
Include ~/Pictures/ in Tier 4 Yes (33 MB) Sush confirmed not personal/sensitive enough to keep separate. Useful on new device.
Include Azure CLI auth Yes (104 MB) Even though tokens are DPAPI-encrypted (won't work on new device), the subscription/profile config is reusable and saves time on az account list/set
Include OBS profile Yes (18 MB) Sush records YouTube content; recreating scenes/sources from scratch is painful
Daily at 9 AM (v1) → twice daily (v3) Twice daily Already twice-daily in practice; documented now
Add bin/ to Tier 3 excludes Yes Mostly .NET build outputs in the repos checked. Risk: misses any meaningful bin/ folders. Acceptable given Sush's projects are mostly Hugo/Astro/JS.

Change log

Date Change
2026-03-27 Initial setup — Corp OneDrive + Google Drive
2026-04-04 Moved from Corp to Personal OneDrive after Purview DLP flagged mcp-config.json (Entra client secrets). Added secrets/ and mcp-config.json to backup.
2026-05-09 v3 redesign — tiered slim model. Cloud footprint 91 GB → ~6.3 GB (93% reduction). Added ssclawy-mirror/ (full C:\ssClawy\ minus build artifacts) and system-mirror/ (Azure CLI, OBS, Pictures). Added small system configs (git, npm, gh, vscode, hosts, PSReadLine, scheduled task XML) to Tier 1 snapshots. Excluded events.jsonl from session backups (~3.7 GB savings). Added privacy guardrail (customer-research-private/ hard-excluded). All external commands wrapped in 30s timeouts. Retention: 14 most recent + 4 weekly. v1 script preserved as .bak-20260509-0820.

Key concepts

Term What it means
Tier A category of content with a specific backup treatment (snapshots vs live mirror, retention rules, exclusions).
Live mirror One current copy of source data. Reflects current state; if a file is deleted from source, it's deleted from the mirror on the next run. No history.
Versioned snapshot A timestamped point-in-time copy. Multiple snapshots accumulate, oldest auto-pruned. Provides history.
robocopy Robust File Copy — Windows command for copying with retry, mirror, exclusion, and incremental options.
/MIR Mirror mode — destination becomes an exact copy of source. Adds new, updates changed, removes files no longer in source.
/XD <name> Exclude directory by name (matches at any depth).
/XF <pattern> Exclude files matching pattern (anywhere in tree).
/XJ Skip junction points and reparse points (avoids symlink loops).
/XO "Exclude older" — only copy files newer than destination. Useful for incremental re-runs.
ISO 8601 week Calendar week per ISO 8601 standard (Mon–Sun, year-stable). Used for "weekly snapshot" retention.
DPAPI Windows Data Protection API — encrypts data tied to the current user account on the current machine. Why Azure CLI tokens don't transfer to a new device.
Purview DLP Microsoft's Data Loss Prevention — scans Corp OneDrive for sensitive data and blocks/alerts. Reason we use Personal cloud.
Scheduled Task Windows feature for running a script automatically at set times.
Tier 1 snapshot The timestamped versioned config snapshots (~50 MB each).
Tier 2 session-mirror Live mirror of ~/.copilot/session-state/ minus runtime telemetry.
Tier 3 ssclawy-mirror Live mirror of C:\ssClawy\ minus build artifacts and customer-private data.
Tier 4 system-mirror Live mirror of larger system data (Azure CLI, OBS, Pictures).


Summary

Sush's full Copilot CLI environment + working folder + system configs are backed up automatically twice daily to two personal cloud locations using a tiered design. Cloud footprint stays at ~6 GB (93% smaller than v1). Disaster recovery on a new device — Windows, Mac, or cloud devbox — is documented and tested. Privacy guardrail keeps Microsoft customer data off Personal cloud. The script self-heals (timeouts, independent tier execution) and is fully self-restoring (the Scheduled Task XML is in every snapshot).