From 26adccce5b2145039723a3cb025fee68c8c7ae6f Mon Sep 17 00:00:00 2001 From: Josh Knapp Date: Wed, 1 Jul 2026 06:33:02 -0700 Subject: [PATCH] Rename backup archive root so extraction dir mode isn't clobbered The transform used `s,^\./,workspace/,`, which rewrites the workspace *contents* (`./foo` -> `workspace/foo`) but leaves tar's root member as a bare `./`. That `./` entry carries the source root's mode/mtime, and on extraction tar stamps them onto the extraction directory itself. Match the leading `.` instead (`s,^\.,workspace,`) so the root member is renamed `./` -> `workspace`, giving the archive a proper `workspace/` directory entry and no bare `./`. The extraction directory is left untouched. Contents, hidden files, excludes, symlink targets and the `flags=rh` hardlink handling are unchanged. Verified in-container: archive top level is exactly `workspace/` + `home-claude/`, no `./` member, node_modules excluded, extraction into a 0755 dir leaves it 0755, workspace/.git preserved. Co-Authored-By: Claude Opus 4.8 (1M context) --- app/src-tauri/src/commands/file_commands.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/app/src-tauri/src/commands/file_commands.rs b/app/src-tauri/src/commands/file_commands.rs index 200b082..7d4d848 100644 --- a/app/src-tauri/src/commands/file_commands.rs +++ b/app/src-tauri/src/commands/file_commands.rs @@ -206,10 +206,14 @@ pub async fn download_container_backup( // so secrets can't leak through the sanitization fallback. // The `--transform` nests the workspace under `workspace/` (parallel to // `home-claude/`) so an extracted archive has both clearly labeled instead - // of scattering the workspace files into the extraction dir. `flags=rh` - // rewrites regular member names AND hardlink target names (so an intra- - // workspace hardlink pair still resolves on extract) while leaving symlink - // targets untouched (rewriting those would corrupt relative/absolute links). + // of scattering the workspace files into the extraction dir. Rewriting the + // leading `.` (rather than `./`) also renames tar's root member from `./` to + // `workspace`, so the archive carries a proper `workspace/` dir entry rather + // than a bare `./` that would stamp the source root's mode/mtime onto the + // extraction directory. `flags=rh` rewrites regular member names AND + // hardlink target names (so an intra-workspace hardlink pair still resolves + // on extract) while leaving symlink targets untouched (rewriting those would + // corrupt relative/absolute links). let script = r#"set -e STAGE=$(mktemp -d) trap 'rm -rf "$STAGE"' EXIT @@ -227,7 +231,7 @@ if [ -d "$HOME/.claude" ]; then fi tar czf - --ignore-failed-read \ --exclude='*/node_modules' --exclude='*/target' \ - --transform='flags=rh;s,^\./,workspace/,' \ + --transform='flags=rh;s,^\.,workspace,' \ -C "$TC_BACKUP_SRC" . \ -C "$STAGE" home-claude"#; -- 2.52.0