Skip to content

fix(backup): archive integrity cross-check fails on Windows (tar entry path mismatch) #44361

@brettdavies

Description

@brettdavies

Summary

The post-tar archive integrity cross-check in backupCreateCommand fails on Windows when exclude patterns are active (two-step archive path). The check compares buildBackupArchivePath() output (forward-slash POSIX paths) against listArchiveEntries() output, but on Windows the paths don't match — causing all backup-exclude integration tests to fail on Windows CI shard 1.

Reproduction

Consistently reproduces on checks-windows shard 1 in CI. All tests using smartExclude: true or --exclude fail with:

Error: Archive integrity check failed: missing payload for asset
  "C:\Users\runneradmin\AppData\Local\Temp\openclaw-backup-exclude-test-PC5aD4\.openclaw"
  (expected "2026-03-12T00-00-01.000Z-openclaw-backup/payload/windows/C/Users/runneradmin/AppData/Local/Temp/openclaw-backup-exclude-test-PC5aD4/.openclaw")

8 tests fail in src/infra/backup-exclude.integration.test.ts (lines 124, 193, 225, 279, 309, 408, 491, 542).

Root Cause

The post-tar cross-check at src/infra/backup-create.ts:488-505 computes expected archive paths via buildBackupArchivePath(archiveRoot, asset.sourcePath) and compares them against listArchiveEntries() output. On Windows, there is a mismatch between what onWriteEntry writes into the tar (POSIX forward-slash paths) and what listArchiveEntries reads back, likely due to node-tar path normalization on Windows during read.

Relevant code:

  • Cross-check: src/infra/backup-create.ts:488-505
  • Path encoding: src/commands/backup-shared.ts:129-145 (encodeAbsolutePathForBackupArchive / buildBackupArchivePath)
  • Entry remapping: src/infra/backup-create.ts:284-294 (remapArchiveEntryPath)
  • Entry listing: src/commands/backup-shared.ts:49-59 (listArchiveEntries)

Possible Fix

Normalize both sides of the comparison to forward slashes before comparing, e.g.:

const normalizedEntries = archiveEntries.map(e => e.replaceAll("\\", "/"));

Or normalize inside listArchiveEntries itself since tar archives canonically use forward slashes.

CI Evidence

Affected Tests

All tests in src/infra/backup-exclude.integration.test.ts that exercise the two-step archive path (any test with exclude patterns active):

  • --smart-exclude excludes venvs/, models/, logs/, completions/
  • --exclude *.log excludes log files from archive
  • --exclude-file loads and applies patterns to real archive
  • --json output does not include excluded[], only excludedStats
  • excludedStats.totalBytes is correct
  • verify passes when smart-excluded paths are absent from archive
  • in-archive manifest contains excludedStats (not excluded[]) with accurate data
  • archive with excludes passes backup verify (excludedStats-only manifest)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions