Skip to content

Commit 89df2e3

Browse files
sammy-SCmeta-codesync[bot]
authored andcommitted
Fix Buck2 --out permission errors on macOS for claude code (#55736)
Summary: Pull Request resolved: #55736 changelog: [internal] On macOS with EdenFS backed directories, Buck2's --out flag fails with "Operation not permitted" error. This fix works around the issue by: 1. Building to a temp directory outside the repo (os.tmpdir()) 2. Removing extended attributes with `xattr -rc` on macOS Reviewed By: cipolleschi Differential Revision: D92078500 fbshipit-source-id: 437734c068e4bf20a4f8e570e1184565810154d3
1 parent 34fb790 commit 89df2e3

2 files changed

Lines changed: 63 additions & 4 deletions

File tree

private/react-native-fantom/runner/executables/hermesc.js

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
runCommandSync,
2222
} from '../utils';
2323
import fs from 'fs';
24+
import os from 'os';
2425
import path from 'path';
2526

2627
type TesterOptions = Readonly<{
@@ -44,7 +45,13 @@ export function build(options: TesterOptions): void {
4445
return;
4546
}
4647

47-
const tmpPath = destPath + '-' + Date.now();
48+
// Use system temp directory outside the repo to avoid macOS extended
49+
// attribute issues with EdenFS/NFS-backed directories
50+
const tmpPath = path.join(
51+
os.tmpdir(),
52+
`hermesc-${Date.now()}-${process.pid}`,
53+
);
54+
const destTmpPath = destPath + '-' + Date.now() + '-' + process.pid;
4855

4956
try {
5057
const result = runBuck2Sync([
@@ -65,11 +72,33 @@ export function build(options: TesterOptions): void {
6572
return;
6673
}
6774

68-
fs.renameSync(tmpPath, destPath);
75+
// Remove extended attributes to avoid "Operation not permitted" errors
76+
// when copying to EdenFS/NFS-backed directories on macOS
77+
if (os.platform() === 'darwin') {
78+
runCommandSync('xattr', ['-rc', tmpPath]);
79+
}
80+
81+
fs.copyFileSync(tmpPath, destTmpPath);
82+
83+
try {
84+
fs.renameSync(destTmpPath, destPath);
85+
} catch (e: unknown) {
86+
// Another process may have created the file already - that's fine
87+
const code =
88+
typeof e === 'object' && e != null && typeof e.code === 'string'
89+
? e.code
90+
: null;
91+
if (code !== 'EEXIST' && !fs.existsSync(destPath)) {
92+
throw e;
93+
}
94+
}
6995
} finally {
7096
try {
7197
fs.unlinkSync(tmpPath);
7298
} catch {}
99+
try {
100+
fs.unlinkSync(destTmpPath);
101+
} catch {}
73102
}
74103
}
75104

private/react-native-fantom/runner/executables/tester.js

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@ import {
1919
runBuck2,
2020
runBuck2Sync,
2121
runCommand,
22+
runCommandSync,
2223
} from '../utils';
2324
import fs from 'fs';
25+
import os from 'os';
2426
import path from 'path';
2527

2628
const FANTOM_TESTER_BUCK_TARGET =
@@ -47,7 +49,13 @@ export function build(options: TesterOptions): void {
4749
return;
4850
}
4951

50-
const tmpPath = destPath + '-' + Date.now();
52+
// Use system temp directory outside the repo to avoid macOS extended
53+
// attribute issues with EdenFS/NFS-backed directories
54+
const tmpPath = path.join(
55+
os.tmpdir(),
56+
`fantom-tester-${Date.now()}-${process.pid}`,
57+
);
58+
const destTmpPath = destPath + '-' + Date.now() + '-' + process.pid;
5159

5260
try {
5361
const result = runBuck2Sync([
@@ -68,11 +76,33 @@ export function build(options: TesterOptions): void {
6876
return;
6977
}
7078

71-
fs.renameSync(tmpPath, destPath);
79+
// Remove extended attributes to avoid "Operation not permitted" errors
80+
// when copying to EdenFS/NFS-backed directories on macOS
81+
if (os.platform() === 'darwin') {
82+
runCommandSync('xattr', ['-rc', tmpPath]);
83+
}
84+
85+
fs.copyFileSync(tmpPath, destTmpPath);
86+
87+
try {
88+
fs.renameSync(destTmpPath, destPath);
89+
} catch (e: unknown) {
90+
// Another process may have created the file already - that's fine
91+
const code =
92+
typeof e === 'object' && e != null && typeof e.code === 'string'
93+
? e.code
94+
: null;
95+
if (code !== 'EEXIST' && !fs.existsSync(destPath)) {
96+
throw e;
97+
}
98+
}
7299
} finally {
73100
try {
74101
fs.unlinkSync(tmpPath);
75102
} catch {}
103+
try {
104+
fs.unlinkSync(destTmpPath);
105+
} catch {}
76106
}
77107
}
78108

0 commit comments

Comments
 (0)