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.jsonlfiles — 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/andmcp-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¶
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¶
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)¶
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:
- Install Google Drive for Desktop → sign in → mounts as
G:\ - Install base tools (git, node, python, pwsh, gh, az, hugo) via winget
- Restore
~/.copilot/from latest snapshot - Restore system configs (gitconfig, npmrc, gh CLI, VS Code, hosts, PSReadLine) from
system-configs/ - Reinstall VS Code extensions from
vscode-extensions.txt - Restore session history from
session-mirror/ - Restore
C:\ssClawy\fromssclawy-mirror/ - Restore Azure CLI from
system-mirror/azure-cli/ - Re-register the Scheduled Task from XML
npm installin each project that needs itaz loginto 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/hostson Mac (needsudo). - No PowerShell scheduled task — use
cronorlaunchd. Samplelaunchdplist:
<?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-ScheduledTaskorGet-ScheduledTask; remove those calls or guard withif ($IsWindows). Robocopy isn't available — usersync -a --delete --exclude=...instead. - Google Drive for Desktop on Mac mounts under
~/Google Drive/My Drive/, notG:\. Update the$gdBackuppath.
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:
- Sign into GitHub
- Read
environment-manifest.jsonfrom your latest snapshot: git cloneonly the repos you need (Codespaces is typically a single-repo environment)- Copy the relevant config files from
system-configs/into the devbox npm installin 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:
"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.jsonetc, 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.jsonlexclusion 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:
session-state/was included, with allevents.jsonlruntime telemetry. ~4 GB per snapshot.- 30 snapshots × 4 GB = 120 GB ceiling, actual usage hit 91.79 GB.
- Twice-daily was promoted from daily somewhere along the way without updating the doc — so retention dropped from 30 days to 15 days.
- 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:
- 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).
- 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).
- 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. - Build-artifact exclusions in Tier 3. Without exclusions,
C:\ssClawy\is 9.9 GB. With them, 5.7 GB. The 4.2 GB saved isnode_modules+dist+.astro+bin— all reproducible frompackage.jsonetc. - 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. - 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.
- 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.
- 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). |
Related docs¶
- Disaster Recovery & Device Migration Guide — step-by-step restore runbook for each scenario (new Windows, EMU→personal GitHub migration).
- OBS Studio Setup — what's inside the OBS profile that gets backed up in Tier 4.
- Custom Instructions Memory — what's in the
~/.copilot/copilot-instructions*.mdfiles that get backed up in Tier 1.
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).