nx 23.2.0-beta.11 → 23.2.0-beta.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/src/ai/constants.d.ts +15 -0
  2. package/dist/src/ai/constants.js +17 -1
  3. package/dist/src/ai/set-up-ai-agents/set-up-ai-agents.js +44 -16
  4. package/dist/src/ai/utils.d.ts +13 -0
  5. package/dist/src/ai/utils.js +29 -0
  6. package/dist/src/command-line/configure-ai-agents/configure-ai-agents.js +25 -8
  7. package/dist/src/command-line/init/implementation/utils.js +0 -8
  8. package/dist/src/command-line/init/init-v2.js +41 -14
  9. package/dist/src/command-line/reset/reset.js +17 -15
  10. package/dist/src/core/graph/main.js +1 -1
  11. package/dist/src/daemon/client/client.d.ts +0 -7
  12. package/dist/src/daemon/client/client.js +41 -22
  13. package/dist/src/daemon/client/daemon-environment.d.ts +32 -0
  14. package/dist/src/daemon/client/daemon-environment.js +95 -6
  15. package/dist/src/daemon/message-types/daemon-message.d.ts +2 -1
  16. package/dist/src/daemon/message-types/daemon-message.js +25 -2
  17. package/dist/src/daemon/sandbox-socket-hint.d.ts +27 -0
  18. package/dist/src/daemon/sandbox-socket-hint.js +136 -0
  19. package/dist/src/daemon/server/dotenv-graph-changes.d.ts +97 -0
  20. package/dist/src/daemon/server/dotenv-graph-changes.js +284 -0
  21. package/dist/src/daemon/server/handle-client-env.d.ts +8 -0
  22. package/dist/src/daemon/server/handle-client-env.js +92 -0
  23. package/dist/src/daemon/server/handle-outputs-changes.d.ts +10 -0
  24. package/dist/src/daemon/server/handle-outputs-changes.js +86 -0
  25. package/dist/src/daemon/server/project-graph-incremental-recomputation.d.ts +21 -0
  26. package/dist/src/daemon/server/project-graph-incremental-recomputation.js +159 -6
  27. package/dist/src/daemon/server/server.js +19 -48
  28. package/dist/src/daemon/tmp-dir.d.ts +10 -0
  29. package/dist/src/daemon/tmp-dir.js +21 -37
  30. package/dist/src/devkit-internals.d.ts +5 -2
  31. package/dist/src/devkit-internals.js +17 -4
  32. package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
  33. package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
  34. package/dist/src/plugins/js/utils/register.d.ts +11 -0
  35. package/dist/src/plugins/js/utils/register.js +40 -6
  36. package/dist/src/project-graph/plugins/get-plugins.d.ts +27 -1
  37. package/dist/src/project-graph/plugins/get-plugins.js +110 -5
  38. package/dist/src/project-graph/plugins/isolation/enabled.js +1 -4
  39. package/dist/src/project-graph/plugins/isolation/isolated-plugin.d.ts +16 -0
  40. package/dist/src/project-graph/plugins/isolation/isolated-plugin.js +47 -3
  41. package/dist/src/project-graph/plugins/isolation/plugin-worker.js +14 -1
  42. package/dist/src/project-graph/project-graph.js +27 -2
  43. package/dist/src/project-graph/utils/project-configuration/target-defaults.js +47 -21
  44. package/dist/src/tasks-runner/run-command.js +3 -3
  45. package/dist/src/tasks-runner/task-env.d.ts +25 -0
  46. package/dist/src/tasks-runner/task-env.js +39 -0
  47. package/dist/src/utils/cache-directory.d.ts +62 -3
  48. package/dist/src/utils/cache-directory.js +276 -25
  49. package/dist/src/utils/db-connection.js +8 -16
  50. package/dist/src/utils/git-utils.d.ts +4 -0
  51. package/dist/src/utils/git-utils.js +248 -11
  52. package/dist/src/utils/nx-tmp-dir.d.ts +5 -0
  53. package/dist/src/utils/nx-tmp-dir.js +7 -2
  54. package/dist/src/utils/owned-private-dir.d.ts +13 -0
  55. package/dist/src/utils/owned-private-dir.js +39 -0
  56. package/dist/src/utils/permission-errors.d.ts +11 -0
  57. package/dist/src/utils/permission-errors.js +17 -0
  58. package/dist/src/utils/socket-refused-exit-code.d.ts +15 -0
  59. package/dist/src/utils/socket-refused-exit-code.js +18 -0
  60. package/package.json +11 -11
@@ -29,3 +29,18 @@ export declare const nxMcpTomlHeader = "[mcp_servers.\"nx-mcp\"]";
29
29
  * For Nx < 22, uses 'nx-mcp'
30
30
  */
31
31
  export declare function getNxMcpTomlConfig(nxVersion: string): string;
32
+ /**
33
+ * The Nx runtime roots as they belong in a *committed* sandbox allowlist.
34
+ *
35
+ * `~/.nx` is the literal tilde rather than an expanded `NX_HOME_TMP_DIR`: the
36
+ * sandbox expands it per user, which is the property that makes the entry worth
37
+ * committing at all. An absolute home would pin the file to whoever ran the
38
+ * generator.
39
+ *
40
+ * Both are listed because socket resolution walks a chain — `/tmp/.nx` first,
41
+ * `~/.nx` when a peer already owns the shared container. Allowing only the first
42
+ * leaves every user on a machine where someone else got there first silently
43
+ * uncovered. Windows resolves `NX_TMP_DIR` to a machine-specific `%TMP%` and has
44
+ * no sandbox to configure, so the POSIX root is the one that belongs here.
45
+ */
46
+ export declare const NX_ALLOWLIST_ROOTS: readonly ["/tmp/.nx", '~/.nx'];
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.nxMcpTomlHeader = exports.analyticsDomain = exports.getAgentRulesWrapped = exports.rulesRegex = exports.nxRulesMarkerCommentEnd = exports.nxRulesMarkerCommentDescription = exports.nxRulesMarkerCommentStart = void 0;
3
+ exports.NX_ALLOWLIST_ROOTS = exports.nxMcpTomlHeader = exports.analyticsDomain = exports.getAgentRulesWrapped = exports.rulesRegex = exports.nxRulesMarkerCommentEnd = exports.nxRulesMarkerCommentDescription = exports.nxRulesMarkerCommentStart = void 0;
4
4
  exports.agentsMdPath = agentsMdPath;
5
5
  exports.geminiMdPath = geminiMdPath;
6
6
  exports.parseGeminiSettings = parseGeminiSettings;
@@ -13,6 +13,7 @@ exports.getNxMcpTomlConfig = getNxMcpTomlConfig;
13
13
  const path_1 = require("path");
14
14
  const semver_1 = require("semver");
15
15
  const fileutils_1 = require("../utils/fileutils");
16
+ const nx_tmp_dir_1 = require("../utils/nx-tmp-dir");
16
17
  const get_agent_rules_1 = require("./set-up-ai-agents/get-agent-rules");
17
18
  function agentsMdPath(root) {
18
19
  return (0, path_1.join)(root, 'AGENTS.md');
@@ -74,3 +75,18 @@ command = "npx"
74
75
  args = ${args}
75
76
  `;
76
77
  }
78
+ /**
79
+ * The Nx runtime roots as they belong in a *committed* sandbox allowlist.
80
+ *
81
+ * `~/.nx` is the literal tilde rather than an expanded `NX_HOME_TMP_DIR`: the
82
+ * sandbox expands it per user, which is the property that makes the entry worth
83
+ * committing at all. An absolute home would pin the file to whoever ran the
84
+ * generator.
85
+ *
86
+ * Both are listed because socket resolution walks a chain — `/tmp/.nx` first,
87
+ * `~/.nx` when a peer already owns the shared container. Allowing only the first
88
+ * leaves every user on a machine where someone else got there first silently
89
+ * uncovered. Windows resolves `NX_TMP_DIR` to a machine-specific `%TMP%` and has
90
+ * no sandbox to configure, so the POSIX root is the one that belongs here.
91
+ */
92
+ exports.NX_ALLOWLIST_ROOTS = [nx_tmp_dir_1.NX_TMP_DIR_POSIX, '~/.nx'];
@@ -110,11 +110,43 @@ async function setupAiAgentsGeneratorImpl(tree, options) {
110
110
  ...json.enabledPlugins,
111
111
  'nx@nx-claude-plugins': true,
112
112
  },
113
- // Widening the sandbox's egress is only justified while events are
114
- // actually being sent, so it follows the workspace's analytics opt-in.
115
- ...(analyticsEnabled
116
- ? { sandbox: allowAnalyticsDomain(json.sandbox) }
117
- : {}),
113
+ // Allow Nx unix socket usage (daemon, plugin workers, forked processes)
114
+ // through Claude Code's sandbox, and analytics requests when the
115
+ // workspace has them on. Nx also copies its native binary into a cache
116
+ // under the same fixed tmp root before loading it, so the read/write
117
+ // grants cover that too. The root is a fixed /tmp path on macOS and
118
+ // Linux, so these entries are machine-independent and safe to commit.
119
+ sandbox: {
120
+ ...json.sandbox,
121
+ filesystem: {
122
+ ...json.sandbox?.filesystem,
123
+ allowRead: withEntries(json.sandbox?.filesystem?.allowRead, ...constants_1.NX_ALLOWLIST_ROOTS),
124
+ // Covers the whole tmp root, not just the socket dir: the native
125
+ // binary cache lives under it too, and without the cache a running
126
+ // daemon keeps an open handle on the binding inside node_modules,
127
+ // which blocks reinstalling or rebuilding dependencies. What keeps
128
+ // users apart is not this allowlist but the 0700 per-uid directories
129
+ // Nx verifies on every use (see ensureOwnedPrivateDir).
130
+ allowWrite: withEntries(json.sandbox?.filesystem?.allowWrite, ...constants_1.NX_ALLOWLIST_ROOTS),
131
+ },
132
+ network: {
133
+ ...json.sandbox?.network,
134
+ // Egress follows the workspace's analytics opt-in (#36663). The
135
+ // socket grant does not: Nx needs its sockets either way.
136
+ ...(analyticsEnabled
137
+ ? {
138
+ allowedDomains: withEntries(json.sandbox?.network?.allowedDomains, constants_1.analyticsDomain),
139
+ }
140
+ : {}),
141
+ // Covers binding as well as connecting, so a fresh daemon, a plugin
142
+ // worker and a forked task can each create their own socket under
143
+ // these roots. Deliberately scoped rather than `allowAllUnixSockets`,
144
+ // which grants connect access to every socket on the machine —
145
+ // including the Docker and SSH-agent sockets — and grants nothing
146
+ // extra for creating Nx's own.
147
+ allowUnixSockets: withEntries(json.sandbox?.network?.allowUnixSockets, ...constants_1.NX_ALLOWLIST_ROOTS),
148
+ },
149
+ },
118
150
  }));
119
151
  // Clean up .mcp.json (nx-mcp now handled by plugin)
120
152
  const mcpJsonPath = (0, constants_1.claudeMcpJsonPath)(options.directory);
@@ -308,17 +340,13 @@ async function setupAiAgentsGeneratorImpl(tree, options) {
308
340
  };
309
341
  };
310
342
  }
311
- function allowAnalyticsDomain(sandbox) {
312
- const allowedDomains = sandbox?.network?.allowedDomains ?? [];
313
- return {
314
- ...sandbox,
315
- network: {
316
- ...sandbox?.network,
317
- allowedDomains: allowedDomains.includes(constants_1.analyticsDomain)
318
- ? allowedDomains
319
- : [...allowedDomains, constants_1.analyticsDomain],
320
- },
321
- };
343
+ /**
344
+ * The user's existing entries in their original order, plus any of `values`
345
+ * they do not already have. A Set does the deduping, so re-running the
346
+ * generator is a no-op rather than a growing list.
347
+ */
348
+ function withEntries(existing, ...values) {
349
+ return [...new Set([...(existing ?? []), ...values])];
322
350
  }
323
351
  function writeAgentRules(tree, path, writeNxCloudRules) {
324
352
  if (!tree.exists(path)) {
@@ -18,3 +18,16 @@ export declare function getAgentConfigurations(agentsToConsider: Agent[], worksp
18
18
  disabledAgents: AgentConfiguration[];
19
19
  }>;
20
20
  export declare function configureAgents(agents: Agent[], workspaceRoot: string, useLatest?: boolean): Promise<void>;
21
+ /**
22
+ * Explains a permission error thrown while writing agent configuration files.
23
+ *
24
+ * The errno alone does not identify the cause. Agent harnesses (e.g. Claude
25
+ * Code) deny writes to their own settings files from sandboxed shell commands,
26
+ * so `nx configure-ai-agents` run through an agent's bash tool fails with EPERM
27
+ * where the same command succeeds in a terminal — but a root-owned
28
+ * `.claude/settings.json` left by an earlier `sudo nx`, or a read-only
29
+ * checkout, produces the same errno with an unrelated remedy. The sandbox
30
+ * explanation is therefore only offered where a sandbox or an agent is
31
+ * actually detected, and the underlying message is kept either way.
32
+ */
33
+ export declare function agentConfigWriteBlockedLines(error: unknown): string[];
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.agentDisplayMap = exports.supportedAgents = void 0;
4
4
  exports.getAgentConfigurations = getAgentConfigurations;
5
5
  exports.configureAgents = configureAgents;
6
+ exports.agentConfigWriteBlockedLines = agentConfigWriteBlockedLines;
6
7
  const tslib_1 = require("tslib");
7
8
  const fs_1 = require("fs");
8
9
  const path_1 = require("path");
@@ -10,6 +11,7 @@ const configuration_1 = require("../config/configuration");
10
11
  const tree_1 = require("../generators/tree");
11
12
  const native_1 = require("../native");
12
13
  const fileutils_1 = require("../utils/fileutils");
14
+ const is_sandbox_1 = require("../utils/is-sandbox");
13
15
  const nx_cloud_utils_1 = require("../utils/nx-cloud-utils");
14
16
  const output_1 = require("../utils/output");
15
17
  const constants_1 = require("./constants");
@@ -215,3 +217,30 @@ async function configureAgents(agents, workspaceRoot, useLatest) {
215
217
  modificationResults.messages.forEach((message) => output_1.output.log(message));
216
218
  modificationResults.errors.forEach((error) => output_1.output.error(error));
217
219
  }
220
+ /**
221
+ * Explains a permission error thrown while writing agent configuration files.
222
+ *
223
+ * The errno alone does not identify the cause. Agent harnesses (e.g. Claude
224
+ * Code) deny writes to their own settings files from sandboxed shell commands,
225
+ * so `nx configure-ai-agents` run through an agent's bash tool fails with EPERM
226
+ * where the same command succeeds in a terminal — but a root-owned
227
+ * `.claude/settings.json` left by an earlier `sudo nx`, or a read-only
228
+ * checkout, produces the same errno with an unrelated remedy. The sandbox
229
+ * explanation is therefore only offered where a sandbox or an agent is
230
+ * actually detected, and the underlying message is kept either way.
231
+ */
232
+ function agentConfigWriteBlockedLines(error) {
233
+ const err = error;
234
+ const target = err?.path ?? 'an agent configuration file';
235
+ return [
236
+ `Writing ${target} failed: ${err?.message ?? 'permission denied'}`,
237
+ ...((0, is_sandbox_1.isSandbox)() || (0, native_1.isAiAgent)()
238
+ ? [
239
+ "Agent sandboxes protect their own settings files from writes made by sandboxed shell commands, so this command cannot finish when run through an agent's bash tool.",
240
+ 'To complete the AI agent setup, run `nx configure-ai-agents` from a regular terminal and relaunch the agent harness.',
241
+ ]
242
+ : [
243
+ 'Check the ownership and mode of that path: an earlier `sudo nx` run or a read-only checkout produces this.',
244
+ ]),
245
+ ];
246
+ }
@@ -10,6 +10,7 @@ const pc = tslib_1.__importStar(require("picocolors"));
10
10
  const constants_1 = require("../../ai/constants");
11
11
  const detect_ai_agent_1 = require("../../ai/detect-ai-agent");
12
12
  const utils_1 = require("../../ai/utils");
13
+ const permission_errors_1 = require("../../utils/permission-errors");
13
14
  const client_1 = require("../../daemon/client/client");
14
15
  const package_json_1 = require("../../utils/package-json");
15
16
  const output_1 = require("../../utils/output");
@@ -214,10 +215,18 @@ async function configureAiAgentsHandlerImpl(options) {
214
215
  }
215
216
  catch (e) {
216
217
  configSpinner.fail('Failed to configure AI agents');
217
- output_1.output.error({
218
- title: 'Error details:',
219
- bodyLines: [e.message],
220
- });
218
+ if ((0, permission_errors_1.isPermissionDenied)(e)) {
219
+ output_1.output.error({
220
+ title: 'Nx was not permitted to write the AI agent configuration',
221
+ bodyLines: (0, utils_1.agentConfigWriteBlockedLines)(e),
222
+ });
223
+ }
224
+ else {
225
+ output_1.output.error({
226
+ title: 'Error details:',
227
+ bodyLines: [e.message],
228
+ });
229
+ }
221
230
  process.exit(1);
222
231
  }
223
232
  }
@@ -310,10 +319,18 @@ async function configureAiAgentsHandlerImpl(options) {
310
319
  }
311
320
  catch (e) {
312
321
  configSpinner.fail('Failed to set up AI agents');
313
- output_1.output.error({
314
- title: 'Error details:',
315
- bodyLines: [e.message],
316
- });
322
+ if ((0, permission_errors_1.isPermissionDenied)(e)) {
323
+ output_1.output.error({
324
+ title: 'Nx was not permitted to write the AI agent configuration',
325
+ bodyLines: (0, utils_1.agentConfigWriteBlockedLines)(e),
326
+ });
327
+ }
328
+ else {
329
+ output_1.output.error({
330
+ title: 'Error details:',
331
+ bodyLines: [e.message],
332
+ });
333
+ }
317
334
  process.exit(1);
318
335
  }
319
336
  }
@@ -196,14 +196,6 @@ function createNxJsonFromTurboJson(turboJson) {
196
196
  nxJson.targetDefaults = targetDefaults;
197
197
  }
198
198
  }
199
- /**
200
- * The fact that cacheDir was in use suggests the user had a reason for deviating from the default.
201
- * We can't know what that reason was, nor if it would still be applicable in Nx, but we can at least
202
- * improve discoverability of the relevant Nx option by explicitly including it with its default value.
203
- */
204
- if (turboJson.cacheDir) {
205
- nxJson.cacheDirectory = '.nx/cache';
206
- }
207
199
  const defaultBase = (0, deduce_default_base_1.deduceDefaultBase)();
208
200
  // Do not add defaultBase if it is inferred to be the Nx default value of main
209
201
  if (defaultBase !== 'main') {
@@ -30,11 +30,14 @@ const utils_1 = require("./implementation/utils");
30
30
  const provenance_1 = require("../../utils/provenance");
31
31
  const handle_import_1 = require("../../utils/handle-import");
32
32
  const native_1 = require("../../native");
33
+ const utils_2 = require("../../ai/utils");
34
+ const permission_errors_1 = require("../../utils/permission-errors");
33
35
  const detect_ai_agent_1 = require("../../ai/detect-ai-agent");
34
36
  const ab_testing_1 = require("../../utils/ab-testing");
35
37
  const analytics_prompt_1 = require("../../utils/analytics-prompt");
36
38
  const is_ci_1 = require("../../utils/is-ci");
37
39
  const ai_output_1 = require("./utils/ai-output");
40
+ const is_sandbox_1 = require("../../utils/is-sandbox");
38
41
  async function initHandler(options, inner = false) {
39
42
  // Use environment variable to force local execution
40
43
  if (process.env.NX_USE_LOCAL === 'true' || inner) {
@@ -119,8 +122,15 @@ async function runInit(options, baseMeta) {
119
122
  }
120
123
  // AI agent mode: apply defaults for non-interactive operation
121
124
  const aiMode = (0, native_1.isAiAgent)();
125
+ const isSandboxed = (0, is_sandbox_1.isSandbox)();
122
126
  if (aiMode) {
123
127
  options.interactive = false; // Force non-interactive
128
+ if (isSandboxed) {
129
+ // During init, before configuring AI agents, the daemon and
130
+ // plugin socket connections are not available
131
+ process.env.NX_DAEMON = 'false';
132
+ process.env.NX_ISOLATE_PLUGINS = 'false';
133
+ }
124
134
  if (options.nxCloud === undefined) {
125
135
  options.nxCloud = false; // Default to skip Nx Cloud
126
136
  }
@@ -320,21 +330,38 @@ async function runInit(options, baseMeta) {
320
330
  await (0, configure_plugins_1.configurePlugins)(pluginsToInstall, updatePackageScripts, pmc, repoRoot, options.verbose);
321
331
  }
322
332
  }
323
- const selectedAgents = await (0, ai_agent_prompts_1.determineAiAgents)(options.aiAgents, options.interactive && guided);
333
+ // AI agent mode configures the detected agent automatically (aiAgents is
334
+ // pre-filled above); interactive humans are always prompted — even in the
335
+ // minimum setup — since agent configuration also writes the sandbox
336
+ // allowances the Nx daemon needs when driven by an agent later.
337
+ const selectedAgents = await (0, ai_agent_prompts_1.determineAiAgents)(options.aiAgents, options.interactive);
324
338
  if (selectedAgents && selectedAgents.length > 0) {
325
- const tree = new tree_1.FsTree(repoRoot, false);
326
- const aiAgentsCallback = await (0, set_up_ai_agents_1.setupAiAgentsGenerator)(tree, {
327
- directory: '.',
328
- writeNxCloudRules: options.nxCloud !== false,
329
- packageVersion: 'latest',
330
- agents: [...selectedAgents],
331
- });
332
- const changes = tree.listChanges();
333
- (0, tree_1.flushChanges)(repoRoot, changes);
334
- if (aiAgentsCallback) {
335
- const results = await aiAgentsCallback();
336
- results.messages.forEach((m) => output_1.output.log(m));
337
- results.errors.forEach((e) => output_1.output.error(e));
339
+ try {
340
+ const tree = new tree_1.FsTree(repoRoot, false);
341
+ const aiAgentsCallback = await (0, set_up_ai_agents_1.setupAiAgentsGenerator)(tree, {
342
+ directory: '.',
343
+ writeNxCloudRules: options.nxCloud !== false,
344
+ packageVersion: 'latest',
345
+ agents: [...selectedAgents],
346
+ });
347
+ const changes = tree.listChanges();
348
+ (0, tree_1.flushChanges)(repoRoot, changes);
349
+ if (aiAgentsCallback) {
350
+ const results = await aiAgentsCallback();
351
+ results.messages.forEach((m) => output_1.output.log(m));
352
+ results.errors.forEach((e) => output_1.output.error(e));
353
+ }
354
+ }
355
+ catch (e) {
356
+ if (!(0, permission_errors_1.isPermissionDenied)(e)) {
357
+ throw e;
358
+ }
359
+ // Don't fail the whole init over this — everything else succeeded.
360
+ // Warned rather than thrown so the rest of the init result still stands.
361
+ output_1.output.warn({
362
+ title: 'AI agent configuration could not be written from this process',
363
+ bodyLines: (0, utils_2.agentConfigWriteBlockedLines)(e),
364
+ });
338
365
  }
339
366
  }
340
367
  let nxCloudChoice;
@@ -8,7 +8,6 @@ const tmp_dir_1 = require("../../daemon/tmp-dir");
8
8
  const cache_directory_1 = require("../../utils/cache-directory");
9
9
  const output_1 = require("../../utils/output");
10
10
  const native_file_cache_location_1 = require("../../native/native-file-cache-location");
11
- const native_1 = require("../../native");
12
11
  const workspace_root_1 = require("../../utils/workspace-root");
13
12
  const client_2 = require("../../nx-cloud/utilities/client");
14
13
  const get_cloud_options_1 = require("../../nx-cloud/utilities/get-cloud-options");
@@ -133,8 +132,19 @@ function removeInstalledNxCloudClient() {
133
132
  function cleanupCacheEntries() {
134
133
  return incrementalBackoff(INCREMENTAL_BACKOFF_FIRST_DELAY, INCREMENTAL_BACKOFF_MAX_DURATION, () => {
135
134
  (0, node_fs_1.rmSync)(cache_directory_1.cacheDir, { recursive: true, force: true });
135
+ // `cacheDir` is the shared directory whenever sharing is available, so
136
+ // this is the checkout's own `.nx/cache`: still there from before the
137
+ // move, and still what a later run falls back to if `~/.nx` stops being
138
+ // reachable. Reset means both.
139
+ removeIfDistinct((0, cache_directory_1.cacheDirectoryForWorkspace)(workspace_root_1.workspaceRoot), cache_directory_1.cacheDir);
136
140
  });
137
141
  }
142
+ /** Skips the no-op when the two resolve to one directory. */
143
+ function removeIfDistinct(dir, from) {
144
+ if (dir !== from) {
145
+ (0, node_fs_1.rmSync)(dir, { recursive: true, force: true });
146
+ }
147
+ }
138
148
  function cleanupNativeFileCache() {
139
149
  return incrementalBackoff(INCREMENTAL_BACKOFF_FIRST_DELAY, INCREMENTAL_BACKOFF_MAX_DURATION, () => {
140
150
  // Null when the native cache root is not a real directory we own, which
@@ -148,20 +158,12 @@ function cleanupNativeFileCache() {
148
158
  function cleanupWorkspaceData() {
149
159
  return incrementalBackoff(INCREMENTAL_BACKOFF_FIRST_DELAY, INCREMENTAL_BACKOFF_MAX_DURATION, () => {
150
160
  (0, node_fs_1.rmSync)(cache_directory_1.workspaceDataDirectory, { recursive: true, force: true });
151
- // If in a worktree, also clean the shared workspace data directory
152
- // in the main repo where the DB actually lives
153
- try {
154
- const mainRoot = (0, native_1.getMainWorktreeRoot)(workspace_root_1.workspaceRoot);
155
- if (mainRoot) {
156
- const sharedDir = (0, cache_directory_1.workspaceDataDirectoryForWorkspace)(mainRoot);
157
- if (sharedDir !== cache_directory_1.workspaceDataDirectory) {
158
- (0, node_fs_1.rmSync)(sharedDir, { recursive: true, force: true });
159
- }
160
- }
161
- }
162
- catch {
163
- // Worktree detection is best-effort during reset
164
- }
161
+ // Also clean wherever the DB actually lives, which is outside this
162
+ // checkout whenever the shared root is reachable. Resolved through the
163
+ // same decision the DB itself uses, so reset cannot delete a directory
164
+ // this process never wrote to -- a sandboxed agent that fell back to its
165
+ // own checkout, or a configured location, yields nothing extra.
166
+ removeIfDistinct((0, cache_directory_1.sharedDataDirectory)(workspace_root_1.workspaceRoot, 'workspace-data'), cache_directory_1.workspaceDataDirectory);
165
167
  });
166
168
  }
167
169
  async function incrementalBackoff(ms, maxDuration, callback) {