fix(subagents): preserve projection portability
- commit
- 81538e1cca9697d3b0b44c2505cbbd9166442d29
- author
- brice · · 2026-08-18 15:16 UTC
- parent
- 5bcc0f8
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +39 -4
M packages/agent/subagents/resident_journal.go +8 -4
| @@ -9,6 +9,7 @@ import ( | |||
| 9 | 9 | "io" | |
| 10 | 10 | "os" | |
| 11 | 11 | "path/filepath" | |
| 12 | + | "runtime" | |
| 12 | 13 | "sort" | |
| 13 | 14 | "strings" | |
| 14 | 15 | "sync" | |
| @@ -1129,10 +1130,13 @@ func writeResidentProjection(dir, name string, value any) error { | |||
| 1129 | 1130 | } | |
| 1130 | 1131 | ||
| 1131 | 1132 | func residentProjectionCurrent(path string, data []byte) bool { | |
| 1132 | - | current, err := os.ReadFile(path) | |
| 1133 | - | if err != nil || !bytes.Equal(current, data) { | |
| 1133 | + | info, err := os.Lstat(path) | |
| 1134 | + | if err != nil || !info.Mode().IsRegular() { | |
| 1135 | + | return false | |
| 1136 | + | } | |
| 1137 | + | if runtime.GOOS != "windows" && info.Mode().Perm() != 0o600 { | |
| 1134 | 1138 | return false | |
| 1135 | 1139 | } | |
| 1136 | - | info, err := os.Stat(path) | |
| 1137 | - | return err == nil && info.Mode().Perm() == 0o600 | |
| 1140 | + | current, err := os.ReadFile(path) | |
| 1141 | + | return err == nil && bytes.Equal(current, data) | |
| 1138 | 1142 | } | |
| @@ -9,6 +9,7 @@ import ( | |||||
| 9 | "io" | 9 | "io" | ||
| 10 | "os" | 10 | "os" | ||
| 11 | "path/filepath" | 11 | "path/filepath" | ||
| 12 | + | "runtime" | |||
| 12 | "sort" | 13 | "sort" | ||
| 13 | "strings" | 14 | "strings" | ||
| 14 | "sync" | 15 | "sync" | ||
| @@ -1129,10 +1130,13 @@ func writeResidentProjection(dir, name string, value any) error { | |||||
| 1129 | } | 1130 | } | ||
| 1130 | 1131 | ||||
| 1131 | func residentProjectionCurrent(path string, data []byte) bool { | 1132 | func residentProjectionCurrent(path string, data []byte) bool { | ||
| 1132 | - | current, err := os.ReadFile(path) | 1133 | + | info, err := os.Lstat(path) |
| 1133 | - | if err != nil || !bytes.Equal(current, data) { | 1134 | + | if err != nil || !info.Mode().IsRegular() { |
| 1135 | + | return false | |||
| 1136 | + | } | |||
| 1137 | + | if runtime.GOOS != "windows" && info.Mode().Perm() != 0o600 { | |||
| 1134 | return false | 1138 | return false | ||
| 1135 | } | 1139 | } | ||
| 1136 | - | info, err := os.Stat(path) | 1140 | + | current, err := os.ReadFile(path) |
| 1137 | - | return err == nil && info.Mode().Perm() == 0o600 | 1141 | + | return err == nil && bytes.Equal(current, data) |
| 1138 | } | 1142 | } | ||
M packages/agent/subagents/resident_journal_test.go +31 -0
| @@ -218,6 +218,37 @@ func TestResidentProjectionSkipsIdenticalRewrite(t *testing.T) { | |||
| 218 | 218 | } | |
| 219 | 219 | } | |
| 220 | 220 | ||
| 221 | + | func TestResidentProjectionReplacesSymlinkInsteadOfFollowingIt(t *testing.T) { | |
| 222 | + | if runtime.GOOS == "windows" { | |
| 223 | + | t.Skip("symlink creation may require elevated privileges on Windows") | |
| 224 | + | } | |
| 225 | + | dir := t.TempDir() | |
| 226 | + | metadata := ResidentMetadata{Version: residentJournalVersion, ID: "projection-child", SessionID: "projection-session", State: ResidentIdle} | |
| 227 | + | data, err := json.Marshal(metadata) | |
| 228 | + | if err != nil { | |
| 229 | + | t.Fatal(err) | |
| 230 | + | } | |
| 231 | + | target := filepath.Join(dir, "target.json") | |
| 232 | + | if err := os.WriteFile(target, data, 0o600); err != nil { | |
| 233 | + | t.Fatal(err) | |
| 234 | + | } | |
| 235 | + | path := filepath.Join(dir, residentMetadataName) | |
| 236 | + | if err := os.Symlink(target, path); err != nil { | |
| 237 | + | t.Fatal(err) | |
| 238 | + | } | |
| 239 | + | ||
| 240 | + | if err := writeResidentMetadata(dir, metadata); err != nil { | |
| 241 | + | t.Fatal(err) | |
| 242 | + | } | |
| 243 | + | info, err := os.Lstat(path) | |
| 244 | + | if err != nil { | |
| 245 | + | t.Fatal(err) | |
| 246 | + | } | |
| 247 | + | if !info.Mode().IsRegular() { | |
| 248 | + | t.Fatalf("projection mode = %v, want regular file", info.Mode()) | |
| 249 | + | } | |
| 250 | + | } | |
| 251 | + | ||
| 221 | 252 | func TestResidentProjectionRepairsPermissionsForIdenticalData(t *testing.T) { | |
| 222 | 253 | if runtime.GOOS == "windows" { | |
| 223 | 254 | t.Skip("POSIX permission bits are not enforced on Windows") | |
| @@ -218,6 +218,37 @@ func TestResidentProjectionSkipsIdenticalRewrite(t *testing.T) { | |||||
| 218 | } | 218 | } | ||
| 219 | } | 219 | } | ||
| 220 | 220 | ||||
| 221 | + | func TestResidentProjectionReplacesSymlinkInsteadOfFollowingIt(t *testing.T) { | |||
| 222 | + | if runtime.GOOS == "windows" { | |||
| 223 | + | t.Skip("symlink creation may require elevated privileges on Windows") | |||
| 224 | + | } | |||
| 225 | + | dir := t.TempDir() | |||
| 226 | + | metadata := ResidentMetadata{Version: residentJournalVersion, ID: "projection-child", SessionID: "projection-session", State: ResidentIdle} | |||
| 227 | + | data, err := json.Marshal(metadata) | |||
| 228 | + | if err != nil { | |||
| 229 | + | t.Fatal(err) | |||
| 230 | + | } | |||
| 231 | + | target := filepath.Join(dir, "target.json") | |||
| 232 | + | if err := os.WriteFile(target, data, 0o600); err != nil { | |||
| 233 | + | t.Fatal(err) | |||
| 234 | + | } | |||
| 235 | + | path := filepath.Join(dir, residentMetadataName) | |||
| 236 | + | if err := os.Symlink(target, path); err != nil { | |||
| 237 | + | t.Fatal(err) | |||
| 238 | + | } | |||
| 239 | + | ||||
| 240 | + | if err := writeResidentMetadata(dir, metadata); err != nil { | |||
| 241 | + | t.Fatal(err) | |||
| 242 | + | } | |||
| 243 | + | info, err := os.Lstat(path) | |||
| 244 | + | if err != nil { | |||
| 245 | + | t.Fatal(err) | |||
| 246 | + | } | |||
| 247 | + | if !info.Mode().IsRegular() { | |||
| 248 | + | t.Fatalf("projection mode = %v, want regular file", info.Mode()) | |||
| 249 | + | } | |||
| 250 | + | } | |||
| 251 | + | ||||
| 221 | func TestResidentProjectionRepairsPermissionsForIdenticalData(t *testing.T) { | 252 | func TestResidentProjectionRepairsPermissionsForIdenticalData(t *testing.T) { | ||
| 222 | if runtime.GOOS == "windows" { | 253 | if runtime.GOOS == "windows" { | ||
| 223 | t.Skip("POSIX permission bits are not enforced on Windows") | 254 | t.Skip("POSIX permission bits are not enforced on Windows") | ||