<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Backup on ErrorZap</title><link>https://errorzap.com/tags/backup/</link><description>Recent content in Backup on ErrorZap</description><image><title>ErrorZap</title><url>https://errorzap.com/og.png</url><link>https://errorzap.com/og.png</link></image><generator>Hugo</generator><language>en-US</language><copyright>ErrorZap</copyright><lastBuildDate>Wed, 22 Apr 2026 00:00:00 -0600</lastBuildDate><atom:link href="https://errorzap.com/tags/backup/index.xml" rel="self" type="application/rss+xml"/><item><title>The Backup That Hung Forever on OneDrive</title><link>https://errorzap.com/posts/the-backup-that-hung-forever-on-onedrive/</link><pubDate>Wed, 22 Apr 2026 00:00:00 -0600</pubDate><guid>https://errorzap.com/posts/the-backup-that-hung-forever-on-onedrive/</guid><description>A Kopia job sat at 0% all night because it tried to read files that didn&amp;rsquo;t actually exist on disk yet.</description><content:encoded><![CDATA[<figure style="text-align:center;margin:0 0 30px"><img src="hero.png" alt="The Backup That Hung Forever on OneDrive" style="max-width:520px;width:100%;border-radius:14px"/></figure>
<p>The backup was &ldquo;running.&rdquo; It had been running for nine hours.</p>
<p>A client&rsquo;s nightly Kopia job pointed at a user&rsquo;s documents folder. Normally it finished in twenty minutes. This morning it was still chewing: no error, no progress, just a cursor blinking next to a snapshot that had hashed maybe a few hundred files and then gone silent.</p>
<p>No CPU. No disk I/O to speak of. No network throughput. Just a process sitting there, perfectly calm, doing absolutely nothing while claiming to be busy.</p>
<p>That&rsquo;s the worst kind of hang. A crash leaves a body. This left a process that would happily wait until the heat death of the universe.</p>
<h2 id="the-investigation">The investigation</h2>
<p>First instinct: blame the repository. Maybe the backend was wedged, maybe a lock was stuck. Nope. A fresh <code>kopia snapshot create</code> against a different folder ran clean in seconds.</p>
<p>So it wasn&rsquo;t Kopia. It was <em>this folder</em>.</p>
<p>I watched the process with Resource Monitor and caught it: every time the scan touched a particular file, the thread parked in a wait state. Not reading. Not erroring. Waiting on the filesystem to hand back bytes that never came.</p>
<p>Then I actually <em>looked</em> at the folder.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>┌────────────────────────────────────────────────┐
</span></span><span style="display:flex;"><span>│  C:\Users\user\Documents  (OneDrive-synced)     │
</span></span><span style="display:flex;"><span>├──────────────┬─────────────────────────────────┤
</span></span><span style="display:flex;"><span>│  Q1-report   │  ✓  green check  (on this PC)    │
</span></span><span style="display:flex;"><span>│  budget.xlsx │  ✓  green check  (on this PC)    │
</span></span><span style="display:flex;"><span>│  archive\    │  ☁  cloud icon   (online-only)   │ ◄── placeholder
</span></span><span style="display:flex;"><span>│  photos\     │  ☁  cloud icon   (online-only)   │ ◄── placeholder
</span></span><span style="display:flex;"><span>└──────────────┴─────────────────────────────────┘
</span></span><span style="display:flex;"><span>        the cloud-icon files have no bytes on disk
</span></span></code></pre></div><p>Half the tree had little cloud icons instead of green checks. OneDrive <strong>Files On-Demand</strong>. Those files looked real — name, size, timestamp — but the actual content lived in the cloud. On disk they were placeholders.</p>
<h2 id="the-aha">The &ldquo;aha&rdquo;</h2>
<p>OneDrive&rsquo;s on-demand placeholders are implemented with the Windows <strong>cloud files filter driver</strong>, <code>cldflt</code>. When any process opens a placeholder and tries to read it, <code>cldflt</code> intercepts the read and <em>hydrates</em> the file — downloads it from the cloud, transparently, before returning a single byte.</p>
<p>A backup tool is the most naive reader on earth. It opens every file and reads it end to end. So Kopia walks into a folder full of placeholders and triggers a hydration storm: thousands of files getting pulled down on demand, throttled, retried, and in some cases just&hellip; stalling. The read call blocks. The thread parks. Forever.</p>
<p>The backup wasn&rsquo;t broken. It was politely waiting for the entire cloud to fall back onto the disk, one file at a time, through a sync client that had no idea it was under attack.</p>
<figure style="text-align:center;margin:34px 0">
<svg viewBox="0 0 560 220" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Kopia read blocked by cldflt hydration versus VSS snapshot bypass">
<rect x="0" y="0" width="560" height="220" rx="10" fill="#11111b"/>
<rect x="28" y="40" width="120" height="54" rx="8" fill="#1e1e2e" stroke="#45475a"/>
<text x="88" y="64" fill="#cdd6f4" font-family="monospace" font-size="13" text-anchor="middle">Kopia</text>
<text x="88" y="82" fill="#6c7086" font-family="monospace" font-size="11" text-anchor="middle">read()</text>
<rect x="220" y="40" width="130" height="54" rx="8" fill="#1e1e2e" stroke="#f38ba8"/>
<text x="285" y="64" fill="#f38ba8" font-family="monospace" font-size="13" text-anchor="middle">cldflt</text>
<text x="285" y="82" fill="#6c7086" font-family="monospace" font-size="11" text-anchor="middle">hydrate...</text>
<rect x="420" y="40" width="115" height="54" rx="8" fill="#313244" stroke="#45475a"/>
<text x="477" y="64" fill="#fab387" font-family="monospace" font-size="12" text-anchor="middle">cloud</text>
<text x="477" y="82" fill="#6c7086" font-family="monospace" font-size="11" text-anchor="middle">(stalls)</text>
<line x1="148" y1="67" x2="218" y2="67" stroke="#89b4fa" stroke-width="2"/>
<polygon points="218,67 210,63 210,71" fill="#89b4fa"/>
<line x1="350" y1="67" x2="418" y2="67" stroke="#f38ba8" stroke-width="2" stroke-dasharray="5 4"/>
<polygon points="418,67 410,63 410,71" fill="#f38ba8"/>
<text x="285" y="120" fill="#f38ba8" font-family="monospace" font-size="12" text-anchor="middle">x blocks forever</text>
<rect x="28" y="150" width="120" height="46" rx="8" fill="#1e1e2e" stroke="#45475a"/>
<text x="88" y="178" fill="#cdd6f4" font-family="monospace" font-size="13" text-anchor="middle">Kopia</text>
<rect x="220" y="150" width="130" height="46" rx="8" fill="#1e1e2e" stroke="#a6e3a1"/>
<text x="285" y="172" fill="#a6e3a1" font-family="monospace" font-size="13" text-anchor="middle">VSS shadow</text>
<text x="285" y="188" fill="#6c7086" font-family="monospace" font-size="10" text-anchor="middle">stable view</text>
<line x1="148" y1="173" x2="218" y2="173" stroke="#a6e3a1" stroke-width="2"/>
<polygon points="218,173 210,169 210,177" fill="#a6e3a1"/>
<text x="430" y="178" fill="#a6e3a1" font-family="monospace" font-size="12" text-anchor="start">ok: no hydration</text>
</svg>
<figcaption style="color:#6c7086;font-size:14px;margin-top:8px">Reading the live folder triggers cldflt hydration and hangs; reading a VSS shadow skips it entirely.</figcaption>
</figure>
<h2 id="the-fix">The fix</h2>
<p>You don&rsquo;t want to read the live folder. You want to read a <strong>snapshot</strong> of it — a frozen, point-in-time view that the cloud filter doesn&rsquo;t get to intercept. That&rsquo;s exactly what a Volume Shadow Copy gives you. The VSS snapshot presents the volume&rsquo;s state as committed bytes, with no on-demand hydration path.</p>
<p>Kopia has hooks for precisely this: run an action before it walks the source root, and another after. Create the shadow before, tear it down after.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#6272a4"># before-snapshot: create a shadow copy and expose it via symlink</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">$vol</span> = (vssadmin create shadow /<span style="color:#ff79c6">for</span>=C: | <span style="color:#8be9fd;font-style:italic">Select-String</span> <span style="color:#f1fa8c">&#39;Shadow Copy Volume Name&#39;</span>).ToString().Split(<span style="color:#f1fa8c">&#39; &#39;</span>)[<span style="color:#bd93f9">-1</span>]
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">$env:KOPIA_SNAPSHOT_PATH</span> = <span style="color:#8be9fd;font-style:italic">$vol</span>
</span></span><span style="display:flex;"><span>cmd /c <span style="color:#f1fa8c">&#34;mklink /d C:\kopia-shadow </span><span style="color:#8be9fd;font-style:italic">$vol</span><span style="color:#f1fa8c">\&#34;</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#6272a4"># after-snapshot: clean up the link and drop the shadow</span>
</span></span><span style="display:flex;"><span>cmd /c <span style="color:#f1fa8c">&#34;rmdir C:\kopia-shadow&#34;</span>
</span></span><span style="display:flex;"><span>vssadmin delete shadows /<span style="color:#ff79c6">for</span>=C: /oldest /quiet
</span></span></code></pre></div><p>Then point the source at the shadow instead of the live path and wire the hooks in:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>kopia snapshot create C:<span style="color:#f1fa8c">\k</span>opia-shadow<span style="color:#f1fa8c">\U</span>sers<span style="color:#f1fa8c">\u</span>ser<span style="color:#f1fa8c">\D</span>ocuments <span style="color:#f1fa8c">\
</span></span></span><span style="display:flex;"><span>  --before-snapshot-root-action<span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#34;powershell -File C:\scripts\vss-before.ps1&#34;</span> <span style="color:#f1fa8c">\
</span></span></span><span style="display:flex;"><span>  --after-snapshot-root-action<span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#34;powershell -File C:\scripts\vss-after.ps1&#34;</span>
</span></span></code></pre></div><p>The next run finished in eighteen minutes. Every placeholder read off the shadow returned instantly, because the snapshot view doesn&rsquo;t route through <code>cldflt</code>. No hydration, no stalls, no nine-hour ghost.</p>
<h2 id="why-it-happened">Why it happened</h2>
<p>OneDrive Files On-Demand exists so users don&rsquo;t sync 400GB of vacation photos onto a 256GB laptop. Great feature for humans. Terrible surprise for any tool that assumes &ldquo;the file is on this disk.&rdquo;</p>
<p>Kopia did nothing wrong. It read files. The trap is that on a cloud-synced volume, &ldquo;open and read&rdquo; is a network operation in disguise, gated by a filter driver that will block your thread while it phones home. Backups that walk thousands of placeholders turn into a self-inflicted denial of service.</p>
<p>Snapshotting through VSS sidesteps the whole mess, and as a bonus you get a consistent point-in-time copy instead of backing up a folder that&rsquo;s mutating under you.</p>
<h2 id="takeaways">Takeaways</h2>
<ul>
<li><strong>Cloud on-demand files are placeholders, not files.</strong> OneDrive, Dropbox Smart Sync, and friends all use a filter driver (<code>cldflt</code> on Windows) that hydrates on read.</li>
<li><strong>A naive reader hangs on hydration.</strong> Backups, indexers, and AV scanners that open every file will trigger downloads — and stall when the cloud throttles or times out.</li>
<li><strong>Back up from a VSS shadow, not the live tree.</strong> The snapshot view bypasses the cloud filter and returns committed bytes instantly.</li>
<li><strong>Use Kopia&rsquo;s before/after root-action hooks</strong> to create and tear down the shadow around each run — no manual babysitting.</li>
<li><strong>A backup at &ldquo;0% for hours&rdquo; with no I/O isn&rsquo;t slow, it&rsquo;s blocked.</strong> Watch the thread state; a parked read on a cloud volume is the tell.</li>
</ul>
]]></content:encoded></item><item><title>How My Own Backup Deleted My Files</title><link>https://errorzap.com/posts/how-my-own-backup-deleted-my-files/</link><pubDate>Wed, 04 Mar 2026 00:00:00 -0700</pubDate><guid>https://errorzap.com/posts/how-my-own-backup-deleted-my-files/</guid><description>I deleted nine files on purpose — and my backup node helpfully deleted them everywhere else too.</description><content:encoded><![CDATA[<figure style="text-align:center;margin:0 0 30px"><img src="hero.png" alt="How My Own Backup Deleted My Files" style="max-width:520px;width:100%;border-radius:14px"/></figure>
<p>The ticket was boring. Cleanup. A client wanted some stale files gone from their primary share. I deleted them. Coffee. Done.</p>
<p>Twenty minutes later the same nine files vanished from the backup node too.</p>
<p>That&rsquo;s the part that made my stomach drop. The backup node existed <em>precisely</em> so that &ldquo;oops, deleted it on the primary&rdquo; was a non-event. It was supposed to be the safety net. Instead it watched me cut the trapeze and then helpfully cut its own rope to match.</p>
<h2 id="the-investigation">The investigation</h2>
<p>First instinct: ransomware, a rogue cron, a fat-fingered teammate. All wrong. The truth was dumber and worse.</p>
<p>I&rsquo;d inherited this setup and never read the sync config closely. I assumed &ldquo;backup node&rdquo; meant one-way. It didn&rsquo;t.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>grep -i <span style="color:#f1fa8c">&#39;mode\|direction\|propagat&#39;</span> /etc/syncthing/config.xml
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>&lt;folder id=&#34;primary-share&#34; type=&#34;sendreceive&#34; ...&gt;
</span></span></code></pre></div><p><code>sendreceive</code>. Bidirectional. The backup node wasn&rsquo;t a backup at all — it was a <em>mirror</em>. And a mirror does exactly one job: make the other side look like this side. Including the absences.</p>
<p>I deleted nine files on the primary. The sync engine saw nine deletions, decided they were authoritative changes, and faithfully replicated them downstream. No malice. No bug. It did exactly what I configured it to do.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>┌──────────────┐   delete 9 files    ┌──────────────┐
</span></span><span style="display:flex;"><span>│   PRIMARY    │ ──────────────────► │  &#34;BACKUP&#34;    │
</span></span><span style="display:flex;"><span>│  (source)    │   sendreceive       │  node        │
</span></span><span style="display:flex;"><span>└──────────────┘   propagates ✗      └──────────────┘
</span></span><span style="display:flex;"><span>       │                                    │
</span></span><span style="display:flex;"><span>       ▼                                    ▼
</span></span><span style="display:flex;"><span>   files gone                          files gone too
</span></span><span style="display:flex;"><span>                                       ▓ safety net: GONE
</span></span></code></pre></div><p>The &ldquo;aha&rdquo; wasn&rsquo;t a clever diagnostic. It was reading one attribute and feeling like an idiot. <strong>Sync is not backup.</strong> A sync engine has no concept of &ldquo;retain this.&rdquo; Its entire worldview is <em>converge</em>. Deletion is just another change to converge on.</p>
<figure style="text-align:center;margin:34px 0">
<svg viewBox="0 0 560 220" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Bidirectional sync versus pull-only backup">
<defs>
<marker id="arrow" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto">
<path d="M0,0 L6,3 L0,6 Z" fill="#cba6f7"/>
</marker>
</defs>
<rect x="0" y="0" width="560" height="220" fill="#11111b"/>
<text x="280" y="26" fill="#cdd6f4" font-family="monospace" font-size="14" text-anchor="middle">sync mirrors deletions · backup keeps them</text>
<rect x="40" y="60" width="130" height="56" rx="8" fill="#1e1e2e" stroke="#45475a"/>
<text x="105" y="93" fill="#89b4fa" font-family="monospace" font-size="13" text-anchor="middle">PRIMARY</text>
<rect x="390" y="50" width="130" height="34" rx="8" fill="#1e1e2e" stroke="#45475a"/>
<text x="455" y="72" fill="#f38ba8" font-family="monospace" font-size="12" text-anchor="middle">sendreceive ✗</text>
<rect x="390" y="130" width="130" height="34" rx="8" fill="#1e1e2e" stroke="#45475a"/>
<text x="455" y="152" fill="#a6e3a1" font-family="monospace" font-size="12" text-anchor="middle">pull-only ✓</text>
<line x1="170" y1="78" x2="385" y2="67" stroke="#f38ba8" stroke-width="2" marker-end="url(#arrow)"/>
<line x1="385" y1="67" x2="172" y2="88" stroke="#f38ba8" stroke-width="2" stroke-dasharray="4 4" marker-end="url(#arrow)"/>
<text x="278" y="64" fill="#f38ba8" font-family="monospace" font-size="11" text-anchor="middle">delete →</text>
<text x="278" y="100" fill="#f38ba8" font-family="monospace" font-size="11" text-anchor="middle">← delete propagates</text>
<line x1="170" y1="120" x2="385" y2="147" stroke="#a6e3a1" stroke-width="2" marker-end="url(#arrow)"/>
<text x="278" y="142" fill="#a6e3a1" font-family="monospace" font-size="11" text-anchor="middle">receive only, never push</text>
</svg>
<figcaption style="color:#6c7086;font-size:14px;margin-top:8px">A bidirectional mirror converges on deletions. A pull-only backup receives changes but never propagates them back.</figcaption>
</figure>
<h2 id="the-fix">The fix</h2>
<p>Recover first, redesign second. The deleted files came back from a daily snapshot that — by luck, not design — lived <em>off</em> the sync path. Then I changed the node from a mirror into an actual backup.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#6272a4"># 1) flip the backup node to PULL-ONLY: it receives, never pushes</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4">#    Syncthing: set this folder to &#34;Receive Only&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4">#    config.xml -&gt; type=&#34;receiveonly&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># 2) route deletions to a versioned trash instead of hard-deleting</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4">#    &lt;versioning type=&#34;trashcan&#34;&gt; with a retention window</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4">#    (deletes land in .stversions/, not the void)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># 3) shorten the sync interval to bound the blast radius</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4">#    rescanIntervalS: 3600 -&gt; 300   # 5 min, not 1 hour</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># 4) confirm the daily snapshot job runs OUTSIDE the synced tree</span>
</span></span><span style="display:flex;"><span>systemctl list-timers | grep snapshot
</span></span><span style="display:flex;"><span>ls -la /var/snapshots/   <span style="color:#6272a4"># snapshots here, NOT under the synced folder</span>
</span></span></code></pre></div><p>After the flip, the verification was the satisfying part. Delete a test file on the primary, watch the backup node <em>not</em> care:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#6272a4"># on primary</span>
</span></span><span style="display:flex;"><span>touch /srv/share/CANARY.txt
</span></span><span style="display:flex;"><span>rm  /srv/share/CANARY.txt
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># on backup node, a few minutes later</span>
</span></span><span style="display:flex;"><span>ls /srv/backup/share/CANARY.txt
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># CANARY.txt is still there ✓  (delete did not propagate)</span>
</span></span></code></pre></div><p>Receive-only means upstream deletions are treated as <em>conflicts to ignore</em>, not commands to obey. The node now keeps things the primary throws away. That&rsquo;s the whole point of a backup.</p>
<h2 id="why-it-happened">Why it happened</h2>
<p>Because &ldquo;sync&rdquo; and &ldquo;backup&rdquo; use the same verbs — copy, mirror, replicate — people treat them as the same noun. They aren&rsquo;t.</p>
<p>Sync optimizes for <em>agreement</em>: every node looks identical, fast. Backup optimizes for <em>survival</em>: keep a copy even when the source loses it. Those goals conflict the instant you delete something. A mirror resolves the conflict by deleting too. A backup resolves it by holding on.</p>
<p>I&rsquo;d labeled a mirror &ldquo;backup&rdquo; and trusted the label. The config said <code>sendreceive</code> the whole time. I just never looked.</p>
<h2 id="takeaways">Takeaways</h2>
<ul>
<li><strong>Sync is not backup.</strong> Bidirectional sync replicates deletions perfectly — that&rsquo;s a feature, and it will eat your data.</li>
<li><strong>A backup node must be one-way.</strong> Receive-only / pull-only: it accepts changes but never pushes or propagates deletes upstream-to-down.</li>
<li><strong>Route deletions to trash, not the void.</strong> A versioned trashcan with retention turns &ldquo;gone forever&rdquo; into &ldquo;gone for now.&rdquo;</li>
<li><strong>Keep snapshots off the sync path.</strong> If your point-in-time recovery lives inside the synced tree, the sync can delete your recovery too.</li>
<li><strong>Read the config before you trust the label.</strong> <code>grep</code> the one attribute that defines behavior — <code>sendreceive</code> vs <code>receiveonly</code> is the whole story.</li>
</ul>
]]></content:encoded></item><item><title>When Your Backup Client Lies About Its Name</title><link>https://errorzap.com/posts/when-your-backup-client-lies-about-its-name/</link><pubDate>Fri, 27 Feb 2026 00:00:00 -0700</pubDate><guid>https://errorzap.com/posts/when-your-backup-client-lies-about-its-name/</guid><description>A green-lit backup agent that hadn&amp;rsquo;t saved a single byte in weeks, because the machine and the server disagreed about what it was called.</description><content:encoded><![CDATA[<figure style="text-align:center;margin:0 0 30px"><img src="hero.png" alt="When Your Backup Client Lies About Its Name" style="max-width:520px;width:100%;border-radius:14px"/></figure>
<p>The dashboard was green. That&rsquo;s the part that still bugs me.</p>
<p>A customer calls because a workstation died and they need yesterday&rsquo;s file back. Easy day. I pull up the backup server, find the machine, and go to restore. Except there&rsquo;s nothing to restore. Last successful backup: never. Not &ldquo;stale.&rdquo; Not &ldquo;failed.&rdquo; <strong>Never.</strong></p>
<p>But the agent was online. Heartbeat OK. Service running OK. Sitting there reporting in like a good little soldier. It just hadn&rsquo;t backed up a damn thing the entire time it had been installed.</p>
<h2 id="the-investigation">The investigation</h2>
<p>First instinct: blame the agent. So I remoted in and read the client logs. The agent was happily completing runs. No errors. It thought it was doing its job.</p>
<p>So now I&rsquo;ve got an agent that says &ldquo;I backed up&rdquo; and a server that says &ldquo;this machine has never backed up.&rdquo; Both swear they&rsquo;re telling the truth. When two systems disagree about reality, the disagreement is almost always about <strong>identity</strong>.</p>
<p>I checked what name the agent was registered under on the server. Then I checked the machine&rsquo;s actual OS hostname.</p>
<p>They didn&rsquo;t match.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>┌──────────────────────────┐        ┌──────────────────────────┐
</span></span><span style="display:flex;"><span>│      THE WORKSTATION      │        │      BACKUP SERVER        │
</span></span><span style="display:flex;"><span>│                           │        │                           │
</span></span><span style="display:flex;"><span>│  hostname:  WS-FRONTDESK  │        │  client:  frontdesk-pc    │
</span></span><span style="display:flex;"><span>│  agent runs OK            │        │  keyed by name ───┐       │
</span></span><span style="display:flex;"><span>│  &#34;backup complete&#34; OK     │        │                   ▼       │
</span></span><span style="display:flex;"><span>└───────────┬──────────────┘        │   ┌───────────────────┐   │
</span></span><span style="display:flex;"><span>            │  reports as            │   │ frontdesk-pc      │   │
</span></span><span style="display:flex;"><span>            │  WS-FRONTDESK ─────────┼──►│ (PHANTOM CLIENT)  │   │
</span></span><span style="display:flex;"><span>            │                        │   │ last backup: none │   │
</span></span><span style="display:flex;"><span>            ▼                        │   └───────────────────┘   │
</span></span><span style="display:flex;"><span>   bytes go ►►► nowhere              │   WS-FRONTDESK: not found │
</span></span><span style="display:flex;"><span>└──────────────────────────┘        └──────────────────────────┘
</span></span></code></pre></div><p>The agent identified itself by the machine&rsquo;s real hostname. The server had the client filed under a different, hand-typed name from install day. So the agent checked in as <code>WS-FRONTDESK</code>, the server looked for a client called <code>WS-FRONTDESK</code>, found nobody, and the actual data went into the void. Meanwhile the <code>frontdesk-pc</code> entry someone created manually just sat there forever, eternally &ldquo;waiting for first backup,&rdquo; looking close enough to green that nobody clocked it.</p>
<h2 id="the-aha">The &ldquo;aha&rdquo;</h2>
<p>Backup identity <strong>is</strong> the hostname. Not a label you pick. Not a friendly name. The hostname.</p>
<p>The server keys everything — auth, storage path, history — off the client name. If that name doesn&rsquo;t equal what the OS reports as <code>hostname</code>, the agent and server are two ships passing in the night, both convinced they&rsquo;re docked.</p>
<figure style="text-align:center;margin:34px 0">
<svg viewBox="0 0 560 220" xmlns="http://www.w3.org/2000/svg" font-family="ui-monospace,monospace">
  <rect x="0" y="0" width="560" height="220" fill="#11111b"/>
  <rect x="40" y="50" width="180" height="120" rx="10" fill="#1e1e2e" stroke="#45475a"/>
  <rect x="340" y="50" width="180" height="120" rx="10" fill="#1e1e2e" stroke="#45475a"/>
  <text x="130" y="78" fill="#cdd6f4" font-size="13" text-anchor="middle">Agent says</text>
  <text x="130" y="104" fill="#a6e3a1" font-size="16" text-anchor="middle">WS-FRONTDESK</text>
  <text x="430" y="78" fill="#cdd6f4" font-size="13" text-anchor="middle">Server expects</text>
  <text x="430" y="104" fill="#f38ba8" font-size="16" text-anchor="middle">frontdesk-pc</text>
  <line x1="220" y1="120" x2="340" y2="120" stroke="#fab387" stroke-width="2" stroke-dasharray="6 5"/>
  <text x="280" y="112" fill="#fab387" font-size="22" text-anchor="middle">≠</text>
  <text x="280" y="150" fill="#6c7086" font-size="12" text-anchor="middle">no match</text>
  <text x="130" y="140" fill="#6c7086" font-size="11" text-anchor="middle">= hostname</text>
  <text x="430" y="140" fill="#6c7086" font-size="11" text-anchor="middle">= typed at install</text>
  <text x="280" y="200" fill="#cba6f7" font-size="13" text-anchor="middle">identity mismatch -> silent backup failure</text>
</svg>
<figcaption style="color:#6c7086;font-size:14px;margin-top:8px">Two systems, two names, zero backups — and a green light the whole time.</figcaption>
</figure>
<h2 id="the-fix">The fix</h2>
<p>Stop guessing. Ask the machine its own name, on the box itself:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#6272a4"># On the workstation — what does the OS actually call itself?</span>
</span></span><span style="display:flex;"><span>hostname
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># Verify what the agent will report</span>
</span></span><span style="display:flex;"><span>[System.Net.Dns]::GetHostName()
</span></span></code></pre></div><p>That gave me <code>WS-FRONTDESK</code>. The server had <code>frontdesk-pc</code>. So I deleted the phantom client, re-registered the machine under its <strong>real</strong> hostname, and pushed the server&rsquo;s preconfigured installer — the one that bakes the correct identity in instead of trusting a human to type it right:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#6272a4"># Remove the dead phantom entry, register the machine by its true hostname,</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># then build the preconfigured client installer the server signs.</span>
</span></span><span style="display:flex;"><span>backup-admin client remove   --name <span style="color:#f1fa8c">&#34;frontdesk-pc&#34;</span>
</span></span><span style="display:flex;"><span>backup-admin client add      --name <span style="color:#f1fa8c">&#34;WS-FRONTDESK&#34;</span>
</span></span><span style="display:flex;"><span>backup-admin installer build --client <span style="color:#f1fa8c">&#34;WS-FRONTDESK&#34;</span> --out ./install-WS-FRONTDESK.exe
</span></span></code></pre></div><p>Ran the signed installer on the box, kicked off a manual run, and watched the server. First full backup completed. Restore point exists. The phantom is gone.</p>
<p>(The other valid fix is to rename the <em>machine</em> to match the registered name — but renaming a production workstation is a bigger blast radius than fixing a backup entry. Match the agent to the host, not the host to a typo.)</p>
<h2 id="why-it-happened">Why it happened</h2>
<p>Someone registered the client by hand and typed a &ldquo;nicer&rdquo; name than the ugly auto-generated hostname. The installer was generic, so identity was never enforced — it trusted the typed name. Two sources of truth, no reconciliation, and a monitoring view that treated &ldquo;client exists and pings&rdquo; as &ldquo;client is protected.&rdquo; Existence isn&rsquo;t protection. A backup you can&rsquo;t restore is just a process eating CPU.</p>
<h2 id="takeaways">Takeaways</h2>
<ul>
<li><strong>Backup identity is the hostname.</strong> The registered client name must equal <code>hostname</code> on the machine — verify it, don&rsquo;t assume it.</li>
<li><strong>A green agent proves nothing.</strong> Online does not mean backing up. The only real green is a recent, restorable recovery point.</li>
<li><strong>Use the server&rsquo;s preconfigured installer.</strong> Hand-typed client names are how phantom clients are born. Bake identity in.</li>
<li><strong>Alert on silence.</strong> Add a &ldquo;no successful backup in N hours&rdquo; check so an empty backup history screams instead of sitting there looking calm.</li>
<li><strong>When two systems disagree about reality, suspect identity first.</strong> Auth, naming, and keys cause more &ldquo;impossible&rdquo; failures than disk or network ever will.</li>
</ul>
]]></content:encoded></item><item><title>The git rm That Deleted 30,000 Files</title><link>https://errorzap.com/posts/the-git-rm-that-deleted-30000-files/</link><pubDate>Wed, 25 Feb 2026 00:00:00 -0700</pubDate><guid>https://errorzap.com/posts/the-git-rm-that-deleted-30000-files/</guid><description>One innocent &lt;code&gt;git rm -r&lt;/code&gt; to untrack a folder, and 30,209 notes vanished off the disk in a single keystroke.</description><content:encoded><![CDATA[<figure style="text-align:center;margin:0 0 30px"><img src="hero.png" alt="The git rm That Deleted 30,000 Files" style="max-width:520px;width:100%;border-radius:14px"/></figure>
<p>A client kept their notes vault in git. Years of markdown. Meeting notes, runbooks, half-finished ideas — the kind of stuff you don&rsquo;t think about until it&rsquo;s gone.</p>
<p>They wanted to stop tracking one big folder. Reasonable. It didn&rsquo;t belong in version control.</p>
<p>So they ran the obvious command:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git rm -r attachments/
</span></span></code></pre></div><p>Git chewed on it for a second and printed a wall of <code>rm 'attachments/...'</code> lines. Looked fine. Then <code>git status</code> showed something that made my stomach drop.</p>
<p>It wasn&rsquo;t just the <code>attachments/</code> folder.</p>
<pre tabindex="0"><code>deleted:    notes/2024/standup.md
deleted:    notes/2024/oncall.md
deleted:    runbooks/dns.md
... 30,206 more ...
</code></pre><p>The working tree was empty. <code>ls</code> came back nearly bare. <strong>30,209 markdown files were gone from disk.</strong> Not staged-for-deletion-someday. Gone. Off the filesystem.</p>
<h2 id="the-investigation">The investigation</h2>
<p>The panic version of this story ends with a restore from last night&rsquo;s backup and a lost afternoon. The calm version starts with one question: <em>where does git actually delete from?</em></p>
<p>People think <code>git rm</code> means &ldquo;stop tracking this file.&rdquo; It doesn&rsquo;t. <code>git rm</code> removes a file from <strong>two</strong> places at once: the index (staging area) and the <strong>working tree</strong> — your actual disk.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>┌──────────────┐   ┌──────────────┐   ┌──────────────┐
</span></span><span style="display:flex;"><span>│ Working Tree │   │    Index     │   │   HEAD/Repo  │
</span></span><span style="display:flex;"><span>│  (your disk) │   │  (staging)   │   │ (last commit)│
</span></span><span style="display:flex;"><span>└──────┬───────┘   └──────┬───────┘   └──────┬───────┘
</span></span><span style="display:flex;"><span>       │                  │                  │
</span></span><span style="display:flex;"><span>  git rm ──► ✗ DELETES ──►│ ✗ stages delete  │ ✓ STILL HERE
</span></span><span style="display:flex;"><span>       │                  │                  │
</span></span><span style="display:flex;"><span>  git rm --cached ─────►  │ ✗ untracks only  │ ✓ STILL HERE
</span></span><span style="display:flex;"><span>       ▼                  ▼                  ▼
</span></span><span style="display:flex;"><span>   file gone          marked rm          recoverable
</span></span></code></pre></div><p>That last column is the whole ballgame. <code>git rm</code> touches the working tree and the index, but it does <strong>not</strong> touch your last commit until you actually commit the deletion.</p>
<p>Nobody had committed yet.</p>
<p>The files were sitting safe in <code>HEAD</code> the entire time, even as the disk looked like a crime scene.</p>
<figure style="text-align:center;margin:34px 0">
<svg viewBox="0 0 560 220" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="git rm deletes the working tree and index, but HEAD still holds every file">
  <rect x="0" y="0" width="560" height="220" fill="#11111b"/>
  <rect x="24" y="40" width="140" height="120" rx="10" fill="#1e1e2e" stroke="#45475a"/>
  <rect x="210" y="40" width="140" height="120" rx="10" fill="#1e1e2e" stroke="#45475a"/>
  <rect x="396" y="40" width="140" height="120" rx="10" fill="#1e1e2e" stroke="#a6e3a1"/>
  <text x="94" y="30" text-anchor="middle" fill="#cdd6f4" font-family="monospace" font-size="13">Working Tree</text>
  <text x="280" y="30" text-anchor="middle" fill="#cdd6f4" font-family="monospace" font-size="13">Index</text>
  <text x="466" y="30" text-anchor="middle" fill="#cdd6f4" font-family="monospace" font-size="13">HEAD (commit)</text>
  <text x="94" y="108" text-anchor="middle" fill="#f38ba8" font-family="monospace" font-size="34">✗</text>
  <text x="280" y="108" text-anchor="middle" fill="#f38ba8" font-family="monospace" font-size="34">✗</text>
  <text x="466" y="108" text-anchor="middle" fill="#a6e3a1" font-family="monospace" font-size="34">✓</text>
  <text x="94" y="138" text-anchor="middle" fill="#6c7086" font-family="monospace" font-size="11">wiped</text>
  <text x="280" y="138" text-anchor="middle" fill="#6c7086" font-family="monospace" font-size="11">staged rm</text>
  <text x="466" y="138" text-anchor="middle" fill="#94e2d5" font-family="monospace" font-size="11">30,209 safe</text>
  <text x="187" y="105" text-anchor="middle" fill="#cba6f7" font-family="monospace" font-size="20">►</text>
  <text x="373" y="105" text-anchor="middle" fill="#fab387" font-family="monospace" font-size="20">►</text>
  <text x="280" y="195" text-anchor="middle" fill="#89b4fa" font-family="monospace" font-size="12">git rm -r <folder>  →  recovery lives in the column on the right</text>
</svg>
<figcaption style="color:#6c7086;font-size:14px;margin-top:8px">The deletion never reached the last commit — that's why this was survivable.</figcaption>
</figure>
<h2 id="the-aha">The aha</h2>
<p>If <code>HEAD</code> still has every file, recovery is just &ldquo;copy them back out of the last commit.&rdquo; No backup tape, no remote clone, no heroics.</p>
<h2 id="the-fix">The fix</h2>
<p>First, confirm the damage scope. Trust nothing, count everything:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git status --porcelain | grep -c <span style="color:#f1fa8c">&#39;^D&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># 30209</span>
</span></span></code></pre></div><p>30,209 staged deletions. Matches the missing files exactly. Good — nothing weird, just one bad command.</p>
<p>Now restore the working tree and reset the index straight from <code>HEAD</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git restore --staged --worktree .
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># or, on older git:</span>
</span></span><span style="display:flex;"><span>git checkout HEAD -- .
</span></span></code></pre></div><p>Verify the files actually came back on disk before believing it:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>find . -name <span style="color:#f1fa8c">&#39;*.md&#39;</span> | wc -l
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># 30209</span>
</span></span></code></pre></div><p>All present. <code>git status</code> clean. If <code>HEAD</code> had been corrupt, the same files were still in the remote — <code>git fetch &amp;&amp; git restore --source=origin/main --worktree .</code> would have done it from there.</p>
<p>Then we did the thing they <em>meant</em> to do — untrack without deleting:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git rm -r --cached attachments/
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">echo</span> <span style="color:#f1fa8c">&#39;attachments/&#39;</span> &gt;&gt; .gitignore
</span></span><span style="display:flex;"><span>git commit -m <span style="color:#f1fa8c">&#34;Stop tracking attachments/ (keep on disk)&#34;</span>
</span></span></code></pre></div><p><code>--cached</code> removes the folder from the index only. The disk is never touched. That one flag is the entire difference between a config tidy-up and a disaster.</p>
<h2 id="why-it-happened">Why it happened</h2>
<p><code>git rm</code> carries a default that doesn&rsquo;t match most people&rsquo;s mental model. The intent was &ldquo;git, please forget this exists.&rdquo; Git heard &ldquo;delete this from the index <em>and</em> the disk,&rdquo; because that&rsquo;s what it does. The <code>-r</code> made it recursive, and the glob pulled in far more than expected.</p>
<p>There was no malice and no bad command syntax. It did exactly what it&rsquo;s documented to do. The gap was understanding that the working tree is a target, not a bystander.</p>
<h2 id="takeaways">Takeaways</h2>
<ul>
<li><strong><code>git rm</code> deletes from your disk.</strong> Use <code>git rm --cached</code> to untrack a file while keeping it on the filesystem — that&rsquo;s almost always what you actually want.</li>
<li><strong>Count before, count after.</strong> <code>find . -name '*.md' | wc -l</code> on both sides of any bulk git op. A number that drops by 30,209 is not a surprise you want at commit time.</li>
<li><strong>Test on ONE file first.</strong> Run the destructive command against a single path, check <code>git status</code> and <code>ls</code>, <em>then</em> scale up. One file lost is a shrug; thirty thousand is a bad day.</li>
<li><strong>Don&rsquo;t run destructive git in a live data directory without a backup.</strong> A notes vault is data, not just a repo. Treat it like one.</li>
<li><strong>Know your three trees.</strong> Working tree, index, HEAD. Recovery is trivial when you know which one still has your files — and panic when you don&rsquo;t.</li>
</ul>
]]></content:encoded></item><item><title>Google Drive and OneDrive ARE NOT backups</title><link>https://errorzap.com/posts/google-drive-and-onedrive-are-not/</link><pubDate>Wed, 22 Oct 2025 21:48:00 -0600</pubDate><guid>https://errorzap.com/posts/google-drive-and-onedrive-are-not/</guid><description>Google Drive and OneDrive are sync tools, not backups.If ransomware hits or files get deleted, they sync that too—straight into the void.☁️ Backup Rule 101</description><content:encoded><![CDATA[<p>Google Drive and OneDrive are sync tools, not backups.</p>
<p>If ransomware hits or files get deleted, they sync that too—straight into the void.</p>
<p>☁️ Backup Rule 101: Always keep at least one offline or immutable copy of your data.</p>
<p>💾 True backup = a separate system that keeps previous versions safe from sync errors or hacks.</p>
<p>#TechTips #BackupStrategy #CyberSecurity #DataProtection</p>
<p><img loading="lazy" src="/posts/google-drive-and-onedrive-are-not/img1.jpg"></p>
]]></content:encoded></item></channel></rss>