@gajae-code/coding-agent 0.15.6 → 0.16.1

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 (270) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/dist/types/async/job-manager.d.ts +32 -1
  3. package/dist/types/browser-backend.d.ts +12 -0
  4. package/dist/types/cli/args.d.ts +2 -0
  5. package/dist/types/cli/root-flags.d.ts +14 -0
  6. package/dist/types/cli/update-cli.d.ts +14 -0
  7. package/dist/types/cli.d.ts +7 -0
  8. package/dist/types/commands/launch.d.ts +10 -0
  9. package/dist/types/commands/sdk.d.ts +2 -0
  10. package/dist/types/config/autorouting-tier-map.d.ts +24 -0
  11. package/dist/types/config/billing-path.d.ts +25 -0
  12. package/dist/types/config/config-schema-version.d.ts +13 -1
  13. package/dist/types/config/file-lock.d.ts +5 -0
  14. package/dist/types/config/model-registry.d.ts +22 -0
  15. package/dist/types/config/settings-schema.d.ts +27 -8
  16. package/dist/types/config/settings.d.ts +2 -0
  17. package/dist/types/coordinator-mcp/projection-scan.d.ts +8 -0
  18. package/dist/types/crash/record-loader.d.ts +15 -0
  19. package/dist/types/extensibility/extensions/types.d.ts +8 -3
  20. package/dist/types/gjc-runtime/launch-worktree-reservation.d.ts +21 -0
  21. package/dist/types/gjc-runtime/launch-worktree.d.ts +25 -1
  22. package/dist/types/gjc-runtime/managed-owner-supervisor.d.ts +2 -0
  23. package/dist/types/gjc-runtime/session-state-lock.d.ts +22 -0
  24. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +66 -1
  25. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +1 -0
  26. package/dist/types/gjc-runtime/tmux-sessions.d.ts +27 -0
  27. package/dist/types/main.d.ts +1 -0
  28. package/dist/types/master-mode/context.d.ts +21 -0
  29. package/dist/types/master-mode/first-request.d.ts +34 -0
  30. package/dist/types/master-mode/snapshot.d.ts +32 -0
  31. package/dist/types/modes/acp/acp-agent.d.ts +21 -21
  32. package/dist/types/modes/components/frictionless-onboarding-selector.d.ts +10 -0
  33. package/dist/types/modes/components/model-selector.d.ts +4 -0
  34. package/dist/types/modes/components/oauth-selector.d.ts +1 -0
  35. package/dist/types/modes/components/session-selector.d.ts +1 -1
  36. package/dist/types/modes/components/sessions-dashboard.d.ts +2 -1
  37. package/dist/types/modes/components/status-line/model-name.d.ts +18 -0
  38. package/dist/types/modes/components/status-line/priority-row.d.ts +34 -0
  39. package/dist/types/modes/components/status-line/segments.d.ts +10 -0
  40. package/dist/types/modes/interactive-mode.d.ts +16 -2
  41. package/dist/types/modes/print-mode.d.ts +17 -0
  42. package/dist/types/modes/theme/theme.d.ts +2 -1
  43. package/dist/types/modes/types.d.ts +2 -0
  44. package/dist/types/modes/ui-language.d.ts +7 -1
  45. package/dist/types/sdk/broker/broker.d.ts +79 -2
  46. package/dist/types/sdk/broker/endpoint-authority.d.ts +24 -0
  47. package/dist/types/sdk/broker/ensure.d.ts +3 -0
  48. package/dist/types/sdk/broker/index.d.ts +3 -0
  49. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +6 -0
  50. package/dist/types/sdk/broker/lifecycle.d.ts +40 -0
  51. package/dist/types/sdk/broker/master-capability.d.ts +10 -0
  52. package/dist/types/sdk/broker/session-index.d.ts +40 -11
  53. package/dist/types/sdk/broker/session-scope.d.ts +72 -0
  54. package/dist/types/sdk/broker/spawn-authority.d.ts +201 -0
  55. package/dist/types/sdk/broker/spawn-substrate.d.ts +30 -0
  56. package/dist/types/sdk/broker/startup-budget.d.ts +52 -0
  57. package/dist/types/sdk/broker/state-version.d.ts +21 -1
  58. package/dist/types/sdk/broker/worktree-occupancy.d.ts +28 -0
  59. package/dist/types/sdk/bus/chat-daemon-control.d.ts +3 -0
  60. package/dist/types/sdk/bus/discord-daemon.d.ts +1 -1
  61. package/dist/types/sdk/bus/index.d.ts +9 -1
  62. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +1 -1
  63. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +1 -1
  64. package/dist/types/sdk/bus/slack-conversation.d.ts +1 -1
  65. package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
  66. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
  67. package/dist/types/sdk/cli/index.d.ts +1 -0
  68. package/dist/types/sdk/cli/master-cli.d.ts +63 -0
  69. package/dist/types/sdk/cli/rows.d.ts +12 -5
  70. package/dist/types/sdk/cli/session-cli.d.ts +20 -3
  71. package/dist/types/sdk/client/client.d.ts +3 -2
  72. package/dist/types/sdk/host/control/dispatch.d.ts +10 -0
  73. package/dist/types/sdk/host/control/index.d.ts +1 -1
  74. package/dist/types/sdk/host/control/operations.d.ts +2 -0
  75. package/dist/types/sdk/host/control/runtime-gate.d.ts +21 -7
  76. package/dist/types/sdk/host/events.d.ts +1 -0
  77. package/dist/types/sdk/host/host.d.ts +10 -0
  78. package/dist/types/sdk/host/query/cursor.d.ts +4 -2
  79. package/dist/types/sdk/host/query/handlers.d.ts +1 -0
  80. package/dist/types/sdk/host/query/revision-store.d.ts +1 -0
  81. package/dist/types/sdk/host/session-runtime.d.ts +70 -17
  82. package/dist/types/sdk/lifecycle/broker-client.d.ts +6 -0
  83. package/dist/types/sdk/lifecycle/service.d.ts +15 -3
  84. package/dist/types/sdk/lifecycle-session.d.ts +3 -1
  85. package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -5
  86. package/dist/types/sdk/receipt-state.d.ts +2 -0
  87. package/dist/types/sdk/router/session-router.d.ts +6 -0
  88. package/dist/types/sdk/session.d.ts +16 -0
  89. package/dist/types/sdk/startup-capability.d.ts +2 -1
  90. package/dist/types/sdk/turn-result.d.ts +6 -0
  91. package/dist/types/session/agent-session.d.ts +52 -7
  92. package/dist/types/session/credential-store-errors.d.ts +1 -0
  93. package/dist/types/session/fold-coordinator.d.ts +29 -2
  94. package/dist/types/session/session-manager.d.ts +79 -2
  95. package/dist/types/session/yield-queue.d.ts +5 -4
  96. package/dist/types/setup/frictionless-onboarding.d.ts +1 -0
  97. package/dist/types/tools/ask.d.ts +1 -0
  98. package/dist/types/tools/bash.d.ts +16 -0
  99. package/dist/types/tools/browser/launch.d.ts +10 -0
  100. package/dist/types/tools/browser/registry.d.ts +8 -0
  101. package/dist/types/tools/gh.d.ts +8 -0
  102. package/dist/types/tools/index.d.ts +10 -10
  103. package/dist/types/tools/job.d.ts +3 -1
  104. package/dist/types/utils/external-editor.d.ts +7 -0
  105. package/examples/extensions/README.md +1 -1
  106. package/examples/extensions/pirate.ts +7 -4
  107. package/package.json +7 -7
  108. package/scripts/generate-sdk-operation-inventory.ts +13 -1
  109. package/scripts/verify-gjc-sdk-canonicalization.ts +35 -1
  110. package/src/async/job-manager.ts +107 -9
  111. package/src/browser-backend.ts +29 -0
  112. package/src/cli/accounts-cli.ts +9 -5
  113. package/src/cli/args.ts +17 -0
  114. package/src/cli/fast-help.ts +1 -0
  115. package/src/cli/plugin-cli.ts +29 -0
  116. package/src/cli/root-flags.ts +5 -0
  117. package/src/cli/update-cli.ts +140 -23
  118. package/src/commands/launch.ts +86 -18
  119. package/src/commands/sdk.ts +135 -28
  120. package/src/config/autorouting-tier-map.ts +12 -0
  121. package/src/config/billing-path.ts +42 -0
  122. package/src/config/config-schema-version.ts +18 -1
  123. package/src/config/file-lock.ts +158 -83
  124. package/src/config/model-preset-registry.ts +178 -23
  125. package/src/config/model-profiles.ts +5 -5
  126. package/src/config/model-registry.ts +291 -42
  127. package/src/config/model-resolver.ts +5 -0
  128. package/src/config/settings-schema.ts +27 -8
  129. package/src/config/settings.ts +51 -15
  130. package/src/coordinator-mcp/projection-scan.ts +27 -6
  131. package/src/coordinator-mcp/question-state.ts +9 -11
  132. package/src/coordinator-mcp/server.ts +115 -21
  133. package/src/crash/index-store.ts +2 -0
  134. package/src/crash/record-loader.ts +10 -0
  135. package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
  136. package/src/extensibility/extensions/runner.ts +27 -0
  137. package/src/extensibility/extensions/types.ts +8 -3
  138. package/src/extensibility/gjc-plugins/installer.ts +51 -11
  139. package/src/extensibility/runtime-skill-discovery.ts +110 -3
  140. package/src/extensibility/skills.ts +22 -12
  141. package/src/gjc-runtime/launch-worktree-reservation.ts +242 -0
  142. package/src/gjc-runtime/launch-worktree.ts +285 -4
  143. package/src/gjc-runtime/managed-owner-supervisor.ts +63 -51
  144. package/src/gjc-runtime/session-state-lock.ts +213 -14
  145. package/src/gjc-runtime/session-state-sidecar.ts +938 -39
  146. package/src/gjc-runtime/tmux-owner-isolation-cli.ts +4 -7
  147. package/src/gjc-runtime/tmux-owner-isolation.ts +18 -3
  148. package/src/gjc-runtime/tmux-sessions.ts +252 -13
  149. package/src/hooks/skill-state.ts +6 -0
  150. package/src/internal-urls/docs-index.generated.ts +15 -15
  151. package/src/internal-urls/issue-pr-protocol.ts +4 -2
  152. package/src/main.ts +61 -3
  153. package/src/master-mode/context.ts +50 -0
  154. package/src/master-mode/first-request.ts +65 -0
  155. package/src/master-mode/snapshot.ts +205 -0
  156. package/src/modes/acp/acp-agent.ts +610 -161
  157. package/src/modes/components/footer.ts +4 -2
  158. package/src/modes/components/frictionless-onboarding-selector.ts +48 -2
  159. package/src/modes/components/model-selector.ts +76 -8
  160. package/src/modes/components/oauth-selector.ts +6 -1
  161. package/src/modes/components/session-selector.ts +23 -15
  162. package/src/modes/components/sessions-dashboard.ts +8 -5
  163. package/src/modes/components/settings-selector.ts +1 -0
  164. package/src/modes/components/status-line/model-name.ts +53 -0
  165. package/src/modes/components/status-line/priority-row.ts +107 -0
  166. package/src/modes/components/status-line/segments.ts +23 -9
  167. package/src/modes/components/tool-status-header.ts +151 -15
  168. package/src/modes/controllers/command-controller.ts +1 -1
  169. package/src/modes/controllers/event-controller.ts +30 -4
  170. package/src/modes/controllers/extension-ui-controller.ts +13 -13
  171. package/src/modes/controllers/input-controller.ts +30 -2
  172. package/src/modes/controllers/selector-controller.ts +200 -98
  173. package/src/modes/interactive-mode.ts +36 -12
  174. package/src/modes/print-mode.ts +64 -0
  175. package/src/modes/prompt-action-autocomplete.ts +30 -4
  176. package/src/modes/runtime-init.ts +12 -9
  177. package/src/modes/theme/theme.ts +5 -0
  178. package/src/modes/types.ts +2 -0
  179. package/src/modes/ui-language.ts +49 -2
  180. package/src/modes/utils/ui-helpers.ts +22 -3
  181. package/src/prompts/agent-fragments/browser-aside.md +22 -0
  182. package/src/prompts/system/master-mode.md +28 -0
  183. package/src/prompts/tools/ask.md +1 -1
  184. package/src/prompts/tools/bash.md +1 -0
  185. package/src/prompts/tools/job.md +1 -1
  186. package/src/sdk/acp/adapter.ts +5 -0
  187. package/src/sdk/broker/broker.ts +1646 -27
  188. package/src/sdk/broker/endpoint-authority.ts +86 -0
  189. package/src/sdk/broker/ensure.ts +81 -3
  190. package/src/sdk/broker/index.ts +3 -0
  191. package/src/sdk/broker/lifecycle-ledger.ts +6 -0
  192. package/src/sdk/broker/lifecycle.ts +440 -75
  193. package/src/sdk/broker/master-capability.ts +232 -0
  194. package/src/sdk/broker/session-index.ts +121 -17
  195. package/src/sdk/broker/session-scope.ts +272 -0
  196. package/src/sdk/broker/spawn-authority.ts +1031 -0
  197. package/src/sdk/broker/spawn-substrate.ts +591 -0
  198. package/src/sdk/broker/startup-budget.ts +144 -2
  199. package/src/sdk/broker/state-version.ts +43 -14
  200. package/src/sdk/broker/transport.ts +1 -0
  201. package/src/sdk/broker/worktree-occupancy.ts +90 -0
  202. package/src/sdk/bus/chat-daemon-control.ts +5 -2
  203. package/src/sdk/bus/chat-daemon-runtime.ts +136 -18
  204. package/src/sdk/bus/discord-daemon.ts +50 -10
  205. package/src/sdk/bus/index.ts +84 -7
  206. package/src/sdk/bus/kind-aware-reconciliation.ts +296 -48
  207. package/src/sdk/bus/lifecycle-commands.ts +4 -0
  208. package/src/sdk/bus/notification-service.ts +22 -5
  209. package/src/sdk/bus/prompt-reconciliation.ts +7 -2
  210. package/src/sdk/bus/reconciliation-store.ts +55 -31
  211. package/src/sdk/bus/slack-conversation.ts +1 -1
  212. package/src/sdk/bus/slack-daemon.ts +72 -38
  213. package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
  214. package/src/sdk/cli/index.ts +1 -0
  215. package/src/sdk/cli/master-cli.ts +259 -0
  216. package/src/sdk/cli/rows.ts +41 -15
  217. package/src/sdk/cli/session-cli.ts +297 -26
  218. package/src/sdk/client/client.ts +14 -8
  219. package/src/sdk/host/control/dispatch.ts +101 -40
  220. package/src/sdk/host/control/index.ts +2 -0
  221. package/src/sdk/host/control/operations.ts +2 -0
  222. package/src/sdk/host/control/runtime-gate.ts +203 -11
  223. package/src/sdk/host/events.ts +1 -0
  224. package/src/sdk/host/host.ts +29 -2
  225. package/src/sdk/host/query/cursor.ts +52 -7
  226. package/src/sdk/host/query/handlers.ts +33 -12
  227. package/src/sdk/host/query/revision-store.ts +12 -0
  228. package/src/sdk/host/session-runtime.test.ts +673 -14
  229. package/src/sdk/host/session-runtime.ts +674 -103
  230. package/src/sdk/host/surface-policy.ts +3 -3
  231. package/src/sdk/lifecycle/broker-client.ts +28 -0
  232. package/src/sdk/lifecycle/service.ts +279 -20
  233. package/src/sdk/lifecycle-session.ts +17 -4
  234. package/src/sdk/prompt-deadline-manager.ts +2 -1
  235. package/src/sdk/protocol/operation-inventory.generated.json +105 -3
  236. package/src/sdk/protocol/operation-registry.ts +14 -4
  237. package/src/sdk/receipt-state.ts +10 -0
  238. package/src/sdk/router/session-router.ts +219 -118
  239. package/src/sdk/session.ts +416 -195
  240. package/src/sdk/startup-capability.ts +1 -0
  241. package/src/sdk/turn-result.ts +22 -0
  242. package/src/session/agent-session.ts +1628 -318
  243. package/src/session/blob-store.ts +15 -5
  244. package/src/session/credential-store-errors.ts +2 -0
  245. package/src/session/fold-coordinator.ts +40 -3
  246. package/src/session/internal/managed-session-storage.ts +153 -78
  247. package/src/session/session-manager.ts +850 -97
  248. package/src/session/yield-queue.ts +24 -10
  249. package/src/setup/frictionless-onboarding.ts +2 -0
  250. package/src/slash-commands/builtin-registry.ts +41 -0
  251. package/src/task/executor.ts +9 -4
  252. package/src/telemetry/events.ts +1138 -12
  253. package/src/tools/ask-contract.test.ts +64 -0
  254. package/src/tools/ask-contract.ts +36 -1
  255. package/src/tools/ask.ts +10 -8
  256. package/src/tools/bash-interactive.ts +11 -2
  257. package/src/tools/bash.ts +412 -75
  258. package/src/tools/browser/launch.ts +37 -0
  259. package/src/tools/browser/registry.ts +99 -9
  260. package/src/tools/cron.ts +6 -4
  261. package/src/tools/descriptors.test.ts +15 -0
  262. package/src/tools/descriptors.ts +5 -1
  263. package/src/tools/gh.ts +104 -48
  264. package/src/tools/github-cache.ts +2 -2
  265. package/src/tools/index.ts +10 -6
  266. package/src/tools/job.ts +4 -1
  267. package/src/tools/monitor.ts +12 -9
  268. package/src/tools/resolve.ts +30 -10
  269. package/src/tools/tool-catalog.generated.ts +2 -2
  270. package/src/utils/external-editor.ts +72 -1
@@ -1,6 +1,9 @@
1
1
  import { randomUUID } from "node:crypto";
2
+ import type { BigIntStats } from "node:fs";
2
3
  import * as fs from "node:fs/promises";
3
4
  import * as path from "node:path";
5
+ import type { NativeExactFileIdentity } from "@gajae-code/natives";
6
+ import * as natives from "@gajae-code/natives";
4
7
  import { getTrustedAgentFile } from "@gajae-code/utils";
5
8
 
6
9
  export const TELEMETRY_SCHEMA_VERSION = 1 as const;
@@ -25,7 +28,6 @@ export interface TelemetryEvent {
25
28
  result?: "available" | "up_to_date" | "installed" | "failed" | "skipped";
26
29
  installMethod?: "bun" | "npm" | "binary" | "migrate";
27
30
  }
28
-
29
31
  type EventInput = {
30
32
  event?: unknown;
31
33
  installId?: unknown;
@@ -42,6 +44,21 @@ const RESULTS = new Set(["available", "up_to_date", "installed", "failed", "skip
42
44
  const INSTALL_METHODS = new Set(["bun", "npm", "binary", "migrate"]);
43
45
  const FORBIDDEN_KEY = /(?:prompt|argv|path|env|secret|account|model|provider|repo|error|hostname|username|machine|ip)/i;
44
46
  const UUID_V4 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
47
+ const INSTALL_ID_CLAIM_TIMEOUT_MS = 2_000;
48
+ const INSTALL_ID_CLAIM_RECOVERY_MARGIN_MS = 500;
49
+ const INSTALL_ID_CLAIM_WAIT_TIMEOUT_MS = INSTALL_ID_CLAIM_TIMEOUT_MS + INSTALL_ID_CLAIM_RECOVERY_MARGIN_MS;
50
+ const INSTALL_ID_CLAIM_LEASE_MS = 1_000;
51
+ const INSTALL_ID_HEARTBEAT_INTERVAL_MS = Math.floor(INSTALL_ID_CLAIM_LEASE_MS / 2);
52
+ const INSTALL_ID_CLAIM_POLL_INITIAL_MS = 25;
53
+ const INSTALL_ID_MAX_SERIALIZED_BYTES = 128;
54
+ const CLAIM_MAX_SERIALIZED_BYTES = 16_384;
55
+ const INSTALL_ID_PARTIAL_GRACE_MS = 250;
56
+ const INSTALL_ID_NOFOLLOW_NONBLOCK_FLAGS =
57
+ process.platform === "win32" ? 0 : (fs.constants.O_NOFOLLOW ?? 0) | (fs.constants.O_NONBLOCK ?? 0);
58
+ const INSTALL_ID_READ_FLAGS = fs.constants.O_RDONLY | INSTALL_ID_NOFOLLOW_NONBLOCK_FLAGS;
59
+ const INSTALL_ID_WRITE_FLAGS = fs.constants.O_RDWR | INSTALL_ID_NOFOLLOW_NONBLOCK_FLAGS;
60
+ const durableInstallIdPaths = new Map<string, { identity: string; promise: Promise<void> }>();
61
+ const scheduledClaimCleanups = new Set<string>();
45
62
 
46
63
  function hasForbiddenKey(value: unknown, seen = new Set<object>()): boolean {
47
64
  if (value === null || typeof value !== "object") return false;
@@ -103,12 +120,1100 @@ export function serializeTelemetryEvent(input: unknown): string {
103
120
  return `${JSON.stringify(output)}\n`;
104
121
  }
105
122
 
106
- async function writeNewInstallId(filePath: string, installId: string): Promise<void> {
107
- const handle = await fs.open(filePath, "wx", 0o600);
123
+ async function publishNewInstallId(filePath: string, installId: string): Promise<void> {
124
+ const tempPath = `${filePath}.${randomUUID()}.tmp`;
125
+ let handle: fs.FileHandle | undefined;
126
+ let stagedIdentity: BigIntStats | undefined;
127
+ let cleanupScheduled = false;
108
128
  try {
129
+ handle = await fs.open(tempPath, "wx", 0o600);
109
130
  await handle.writeFile(`${installId}\n`, "utf8");
131
+ await handle.sync();
132
+ stagedIdentity = await handle.stat({ bigint: true });
133
+ } finally {
134
+ await handle?.close().catch(() => undefined);
135
+ if (stagedIdentity === undefined) {
136
+ const failedIdentity = await fs.lstat(tempPath, { bigint: true }).catch(() => undefined);
137
+ if (failedIdentity !== undefined) {
138
+ const failedContent = (await readStagedContentBounded(tempPath)) ?? "";
139
+ scheduleExactStagedCleanup(tempPath, failedIdentity, failedContent);
140
+ cleanupScheduled = true;
141
+ }
142
+ }
143
+ }
144
+ try {
145
+ try {
146
+ await fs.link(tempPath, filePath);
147
+ } catch (error) {
148
+ if (isHardLinkUnsupported(error)) {
149
+ const unsupported = new Error("exclusive hard links are unavailable") as NodeJS.ErrnoException;
150
+ unsupported.code = "EUNSUPPORTED";
151
+ throw unsupported;
152
+ }
153
+ throw error;
154
+ }
155
+ try {
156
+ stagedIdentity = await fs.lstat(tempPath, { bigint: true });
157
+ } catch (error) {
158
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
159
+ }
160
+ await syncDirectory(path.dirname(filePath));
161
+ if (stagedIdentity === undefined) throw claimRaceError();
162
+ let published: PersistedInstallIdSnapshot;
163
+ try {
164
+ published = await readPublishedInstallIdSnapshot(filePath);
165
+ } catch (error) {
166
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") throw claimRaceError();
167
+ throw error;
168
+ }
169
+ if (
170
+ published.value !== installId ||
171
+ published.stat.dev !== stagedIdentity.dev ||
172
+ published.stat.ino !== stagedIdentity.ino ||
173
+ published.stat.size !== stagedIdentity.size ||
174
+ published.stat.mtimeNs !== stagedIdentity.mtimeNs
175
+ )
176
+ throw claimRaceError();
177
+ } finally {
178
+ if (stagedIdentity !== undefined && !cleanupScheduled)
179
+ scheduleExactStagedCleanup(tempPath, stagedIdentity, `${installId}\n`);
180
+ }
181
+ }
182
+
183
+ function scheduleExactStagedCleanup(filePath: string, identity: BigIntStats, content: string): void {
184
+ scheduleExactDetachedCleanup(filePath, {
185
+ dev: identity.dev,
186
+ ino: identity.ino,
187
+ nlink: identity.nlink,
188
+ size: identity.size,
189
+ mtimeNs: identity.mtimeNs,
190
+ sha256: hashClaimBytes(content),
191
+ allowHardLink: true,
192
+ quarantineName: `.${path.basename(filePath)}.${randomUUID()}.quarantine`,
193
+ });
194
+ }
195
+
196
+ function scheduleExactDetachedCleanup(pathname: string, identity: NativeExactFileIdentity, attempt = 0): void {
197
+ if (natives.exactUnlinkDirectDetached(pathname, identity) || attempt >= 3) return;
198
+ const retry = setTimeout(() => scheduleExactDetachedCleanup(pathname, identity, attempt + 1), 25 * (attempt + 1));
199
+ retry.unref();
200
+ }
201
+
202
+ function isHardLinkUnsupported(error: unknown): boolean {
203
+ const code = (error as NodeJS.ErrnoException).code;
204
+ return code === "EOPNOTSUPP" || code === "ENOTSUP" || code === "EPERM" || code === "ENOSYS";
205
+ }
206
+
207
+ async function syncDirectory(directory: string): Promise<void> {
208
+ let handle: fs.FileHandle;
209
+ try {
210
+ handle = await fs.open(directory, "r");
211
+ } catch (error) {
212
+ if (isUnsupportedDirectorySync(error)) return;
213
+ throw error;
214
+ }
215
+ try {
216
+ try {
217
+ await handle.sync();
218
+ } catch (error) {
219
+ if (!isUnsupportedDirectorySync(error)) throw error;
220
+ }
221
+ } finally {
222
+ await handle.close();
223
+ }
224
+ }
225
+
226
+ async function openRegularFileNoFollow(
227
+ filePath: string,
228
+ flags: number,
229
+ failureMessage: string,
230
+ failureCode?: string,
231
+ ): Promise<fs.FileHandle> {
232
+ let named: BigIntStats | undefined;
233
+ if (process.platform === "win32") {
234
+ named = await fs.lstat(filePath, { bigint: true });
235
+ if (!named.isFile()) {
236
+ const error = new Error(failureMessage) as NodeJS.ErrnoException;
237
+ if (failureCode !== undefined) error.code = failureCode;
238
+ throw error;
239
+ }
240
+ }
241
+ const handle = await fs.open(filePath, flags);
242
+ if (named === undefined) return handle;
243
+ try {
244
+ const opened = await handle.stat({ bigint: true });
245
+ if (!opened.isFile() || opened.dev !== named.dev || opened.ino !== named.ino) throw claimRaceError();
246
+ return handle;
247
+ } catch (error) {
248
+ await handle.close().catch(() => undefined);
249
+ throw error;
250
+ }
251
+ }
252
+
253
+ async function openInstallIdFile(filePath: string, flags: number): Promise<fs.FileHandle> {
254
+ return openRegularFileNoFollow(filePath, flags, "telemetry install ID is malformed");
255
+ }
256
+
257
+ async function openClaimFile(claimPath: string, flags: number): Promise<fs.FileHandle> {
258
+ return openRegularFileNoFollow(claimPath, flags, "telemetry install ID claim is nonregular", "ECLAIMNONREGULAR");
259
+ }
260
+
261
+ async function syncFile(filePath: string, expected?: BigIntStats): Promise<void> {
262
+ const handle = await openInstallIdFile(filePath, INSTALL_ID_WRITE_FLAGS);
263
+ try {
264
+ const stat = await handle.stat({ bigint: true });
265
+ if (
266
+ expected !== undefined &&
267
+ (stat.dev !== expected.dev ||
268
+ stat.ino !== expected.ino ||
269
+ stat.size !== expected.size ||
270
+ stat.mtimeNs !== expected.mtimeNs)
271
+ )
272
+ throw claimRaceError();
273
+ await handle.sync();
274
+ } finally {
275
+ await handle.close();
276
+ }
277
+ }
278
+
279
+ async function chmodWinner(filePath: string, expected: BigIntStats): Promise<boolean> {
280
+ let handle: fs.FileHandle;
281
+ let readOnly = false;
282
+ try {
283
+ handle = await openInstallIdFile(filePath, INSTALL_ID_WRITE_FLAGS);
284
+ } catch (error) {
285
+ if ((error as NodeJS.ErrnoException).code !== "EACCES") throw error;
286
+ handle = await openInstallIdFile(filePath, INSTALL_ID_READ_FLAGS);
287
+ readOnly = true;
288
+ }
289
+ try {
290
+ const opened = await handle.stat({ bigint: true });
291
+ if (
292
+ opened.dev !== expected.dev ||
293
+ opened.ino !== expected.ino ||
294
+ opened.size !== expected.size ||
295
+ opened.mtimeNs !== expected.mtimeNs
296
+ )
297
+ return false;
298
+ await handle.chmod(0o600);
299
+ if (readOnly) {
300
+ await handle.close();
301
+ handle = await openInstallIdFile(filePath, INSTALL_ID_WRITE_FLAGS);
302
+ const reopened = await handle.stat({ bigint: true });
303
+ if (reopened.dev !== expected.dev || reopened.ino !== expected.ino) return false;
304
+ }
305
+ await handle.sync();
306
+ const settled = await handle.stat({ bigint: true });
307
+ return (
308
+ settled.dev === expected.dev &&
309
+ settled.ino === expected.ino &&
310
+ settled.size === expected.size &&
311
+ settled.mtimeNs === expected.mtimeNs &&
312
+ (process.platform === "win32" || (settled.mode & 0o777n) === 0o600n)
313
+ );
314
+ } finally {
315
+ await handle.close();
316
+ }
317
+ }
318
+
319
+ async function tightenInstallIdPermissions(filePath: string, expectedValue: string): Promise<void> {
320
+ try {
321
+ const snapshot = await readPublishedInstallIdSnapshot(filePath);
322
+ if (snapshot.value !== expectedValue || !(await chmodWinner(filePath, snapshot.stat))) throw claimRaceError();
323
+ const settled = await readPublishedInstallIdSnapshot(filePath);
324
+ if (
325
+ settled.value !== expectedValue ||
326
+ settled.stat.dev !== snapshot.stat.dev ||
327
+ settled.stat.ino !== snapshot.stat.ino ||
328
+ settled.stat.size !== snapshot.stat.size ||
329
+ settled.stat.mtimeNs !== snapshot.stat.mtimeNs
330
+ )
331
+ throw claimRaceError();
332
+ } catch (error) {
333
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") throw claimRaceError();
334
+ throw error;
335
+ }
336
+ }
337
+
338
+ function isUnsupportedDirectorySync(error: unknown): boolean {
339
+ const code = (error as NodeJS.ErrnoException).code;
340
+ return process.platform === "win32" && (code === "EPERM" || code === "EACCES");
341
+ }
342
+
343
+ type PersistedInstallIdSnapshot = { value: string; stat: BigIntStats };
344
+
345
+ async function readPublishedInstallIdSnapshot(filePath: string): Promise<PersistedInstallIdSnapshot> {
346
+ let handle: fs.FileHandle | undefined;
347
+ try {
348
+ handle = await openInstallIdFile(filePath, INSTALL_ID_READ_FLAGS);
349
+ const stat = await handle.stat({ bigint: true });
350
+ if (!stat.isFile() || stat.size > BigInt(INSTALL_ID_MAX_SERIALIZED_BYTES))
351
+ throw new Error("telemetry install ID is malformed");
352
+ const bytes = Buffer.alloc(Number(stat.size));
353
+ let offset = 0;
354
+ while (offset < bytes.length) {
355
+ const result = await handle.read(bytes, offset, bytes.length - offset, offset);
356
+ if (result.bytesRead <= 0) throw new Error("telemetry install ID is malformed");
357
+ offset += result.bytesRead;
358
+ }
359
+ const settled = await handle.stat({ bigint: true });
360
+ if (settled.size !== stat.size) throw claimRaceError();
361
+ if (settled.size > BigInt(INSTALL_ID_MAX_SERIALIZED_BYTES)) throw new Error("telemetry install ID is malformed");
362
+ const verification = Buffer.alloc(Number(settled.size));
363
+ let verificationOffset = 0;
364
+ while (verificationOffset < verification.length) {
365
+ const result = await handle.read(
366
+ verification,
367
+ verificationOffset,
368
+ verification.length - verificationOffset,
369
+ verificationOffset,
370
+ );
371
+ if (result.bytesRead <= 0) throw claimRaceError();
372
+ verificationOffset += result.bytesRead;
373
+ }
374
+ if (!verification.equals(bytes)) throw claimRaceError();
375
+ const named = await fs.lstat(filePath, { bigint: true });
376
+ if (
377
+ settled.dev !== stat.dev ||
378
+ settled.ino !== stat.ino ||
379
+ settled.size !== stat.size ||
380
+ settled.mtimeNs !== stat.mtimeNs ||
381
+ named.dev !== settled.dev ||
382
+ named.ino !== settled.ino
383
+ )
384
+ throw claimRaceError();
385
+ const existing = bytes.toString("utf8").trim();
386
+ if (!UUID_V4.test(existing)) throw new Error("telemetry install ID is malformed");
387
+ return { value: existing, stat: settled };
388
+ } catch (error) {
389
+ if ((error as NodeJS.ErrnoException).code === "ELOOP") throw new Error("telemetry install ID is malformed");
390
+ throw error;
391
+ } finally {
392
+ await handle?.close().catch(() => undefined);
393
+ }
394
+ }
395
+
396
+ async function readPublishedInstallId(filePath: string): Promise<string> {
397
+ return (await readPublishedInstallIdSnapshot(filePath)).value;
398
+ }
399
+
400
+ async function readStagedContentBounded(filePath: string): Promise<string | undefined> {
401
+ let handle: fs.FileHandle | undefined;
402
+ try {
403
+ handle = await openRegularFileNoFollow(filePath, INSTALL_ID_READ_FLAGS, "telemetry staged file is nonregular");
404
+ const identity = await handle.stat({ bigint: true });
405
+ if (!identity.isFile() || identity.size > BigInt(CLAIM_MAX_SERIALIZED_BYTES)) return undefined;
406
+ const bytes = await readClaimHandleBounded(handle, identity.size);
407
+ const settled = await handle.stat({ bigint: true });
408
+ const named = await fs.lstat(filePath, { bigint: true });
409
+ if (
410
+ settled.dev !== identity.dev ||
411
+ settled.ino !== identity.ino ||
412
+ settled.size !== identity.size ||
413
+ settled.mtimeNs !== identity.mtimeNs ||
414
+ named.dev !== settled.dev ||
415
+ named.ino !== settled.ino ||
416
+ named.size !== settled.size ||
417
+ named.mtimeNs !== settled.mtimeNs
418
+ )
419
+ return undefined;
420
+ return bytes;
421
+ } catch {
422
+ return undefined;
423
+ } finally {
424
+ await handle?.close().catch(() => undefined);
425
+ }
426
+ }
427
+
428
+ async function readExistingInstallId(filePath: string): Promise<string> {
429
+ const deadline = performance.now() + INSTALL_ID_CLAIM_WAIT_TIMEOUT_MS;
430
+ while (true) {
431
+ try {
432
+ return await readExistingInstallIdSnapshot(filePath);
433
+ } catch (error) {
434
+ if (error instanceof Error && error.message === "telemetry install ID is malformed") {
435
+ const stat = await fs.lstat(filePath, { bigint: true });
436
+ if (Date.now() - Number(stat.mtimeMs) > INSTALL_ID_PARTIAL_GRACE_MS) throw error;
437
+ if (performance.now() >= deadline) throw error;
438
+ await Bun.sleep(INSTALL_ID_CLAIM_POLL_INITIAL_MS);
439
+ continue;
440
+ }
441
+ if ((error as NodeJS.ErrnoException).code !== "ECLAIMRACE") throw error;
442
+ if (performance.now() >= deadline) throw new Error("telemetry install ID claim did not clear");
443
+ await Bun.sleep(INSTALL_ID_CLAIM_POLL_INITIAL_MS);
444
+ }
445
+ }
446
+ }
447
+
448
+ async function readExistingInstallIdSnapshot(filePath: string): Promise<string> {
449
+ const claimPath = `${filePath}.lock`;
450
+ const claimBefore = await readClaimIdentity(claimPath);
451
+ let fileMissing = false;
452
+ let malformed: Error | undefined;
453
+ try {
454
+ await readPublishedInstallId(filePath);
455
+ const claimAfter = await readClaimIdentity(claimPath);
456
+ if (claimBefore?.state === "publishing" && UUID_V4.test(claimBefore.token) && claimAfter?.state === undefined)
457
+ throw claimLostError();
458
+ if (claimBefore === undefined && claimAfter === undefined)
459
+ return readPublishedInstallIdWhenUnclaimed(filePath, claimPath);
460
+ if (claimBefore !== undefined && claimAfter === undefined)
461
+ return readPublishedInstallIdWhenUnclaimed(filePath, claimPath);
462
+ } catch (error) {
463
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") fileMissing = true;
464
+ else if (error instanceof Error && error.message === "telemetry install ID is malformed") malformed = error;
465
+ else throw error;
466
+ }
467
+ const activeClaim = await readClaimIdentity(claimPath);
468
+ if (activeClaim === undefined) {
469
+ if (malformed !== undefined) throw malformed;
470
+ if (fileMissing) {
471
+ const missing = new Error("telemetry install ID is missing") as NodeJS.ErrnoException;
472
+ missing.code = "ENOENT";
473
+ throw missing;
474
+ }
475
+ return readPublishedInstallIdWhenUnclaimed(filePath, claimPath);
476
+ }
477
+ if (claimBefore === undefined && activeClaim.state === undefined) throw claimLostError();
478
+ await waitForClaimRelease(claimPath);
479
+ return readPublishedInstallIdWhenUnclaimed(filePath, claimPath);
480
+ }
481
+
482
+ type ClaimState = "publishing" | "committed" | undefined;
483
+ type ClaimIdentity = {
484
+ dev: bigint;
485
+ ino: bigint;
486
+ mtimeMs: number;
487
+ size: bigint;
488
+ digest: string;
489
+ token: string;
490
+ state: ClaimState;
491
+ expiresAt: number | undefined;
492
+ };
493
+
494
+ async function readClaimIdentity(claimPath: string): Promise<ClaimIdentity | undefined> {
495
+ let handle: fs.FileHandle | undefined;
496
+ try {
497
+ handle = await openClaimFile(claimPath, INSTALL_ID_READ_FLAGS);
498
+ const stat = await handle.stat({ bigint: true });
499
+ if (!stat.isFile()) {
500
+ const error = new Error("telemetry install ID claim is nonregular") as NodeJS.ErrnoException;
501
+ error.code = "ECLAIMNONREGULAR";
502
+ throw error;
503
+ }
504
+ if (stat.size > BigInt(CLAIM_MAX_SERIALIZED_BYTES)) {
505
+ const error = new Error("telemetry install ID claim is oversized") as NodeJS.ErrnoException;
506
+ error.code = "ECLAIMOVERSIZE";
507
+ throw error;
508
+ }
509
+ const content = await readClaimHandleBounded(handle, stat.size);
510
+ const settled = await handle.stat({ bigint: true });
511
+ const named = await fs.lstat(claimPath, { bigint: true });
512
+ const verified = await readClaimHandleBounded(handle, settled.size);
513
+ const final = await handle.stat({ bigint: true });
514
+ const finalNamed = await fs.lstat(claimPath, { bigint: true });
515
+ if (
516
+ settled.dev !== stat.dev ||
517
+ settled.ino !== stat.ino ||
518
+ settled.size !== stat.size ||
519
+ settled.mtimeNs !== stat.mtimeNs ||
520
+ named.ino !== settled.ino ||
521
+ hashClaimBytes(content) !== hashClaimBytes(verified) ||
522
+ final.dev !== settled.dev ||
523
+ final.ino !== settled.ino ||
524
+ final.size !== settled.size ||
525
+ final.mtimeNs !== settled.mtimeNs ||
526
+ finalNamed.dev !== final.dev ||
527
+ finalNamed.ino !== final.ino ||
528
+ finalNamed.size !== final.size ||
529
+ finalNamed.mtimeNs !== final.mtimeNs
530
+ )
531
+ throw claimRaceError();
532
+ const parsed = parseClaim(content);
533
+ return {
534
+ dev: settled.dev,
535
+ ino: settled.ino,
536
+ mtimeMs: Number(settled.mtimeMs),
537
+ size: settled.size,
538
+ digest: hashClaimBytes(content),
539
+ token: parsed.token,
540
+ state: parsed.state,
541
+ expiresAt: parsed.expiresAt,
542
+ };
543
+ } catch (error) {
544
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
545
+ throw error;
546
+ } finally {
547
+ await handle?.close().catch(() => undefined);
548
+ }
549
+ }
550
+
551
+ function hashClaimBytes(content: string): string {
552
+ const hash = new Bun.CryptoHasher("sha256");
553
+ hash.update(content);
554
+ return hash.digest("hex");
555
+ }
556
+
557
+ type ClaimSnapshot = { stat: BigIntStats; content: string; digest: string };
558
+
559
+ async function readClaimSnapshot(claimPath: string): Promise<ClaimSnapshot> {
560
+ const handle = await openClaimFile(claimPath, INSTALL_ID_READ_FLAGS);
561
+ try {
562
+ const opened = await handle.stat({ bigint: true });
563
+ if (!opened.isFile()) throw new Error("telemetry install ID claim is nonregular");
564
+ if (opened.size > BigInt(CLAIM_MAX_SERIALIZED_BYTES)) {
565
+ const error = new Error("telemetry install ID claim is oversized") as NodeJS.ErrnoException;
566
+ error.code = "ECLAIMOVERSIZE";
567
+ throw error;
568
+ }
569
+ const content = await readClaimHandleBounded(handle, opened.size);
570
+ const settled = await handle.stat({ bigint: true });
571
+ const named = await fs.lstat(claimPath, { bigint: true });
572
+ if (
573
+ settled.dev !== opened.dev ||
574
+ settled.ino !== opened.ino ||
575
+ settled.size !== opened.size ||
576
+ settled.mtimeNs !== opened.mtimeNs ||
577
+ named.dev !== settled.dev ||
578
+ named.ino !== settled.ino
579
+ )
580
+ throw claimRaceError();
581
+ return { stat: settled, content, digest: hashClaimBytes(content) };
582
+ } finally {
583
+ await handle.close().catch(() => undefined);
584
+ }
585
+ }
586
+
587
+ async function readClaimHandleBounded(handle: fs.FileHandle, size: bigint): Promise<string> {
588
+ const length = Number(size > BigInt(CLAIM_MAX_SERIALIZED_BYTES) ? BigInt(CLAIM_MAX_SERIALIZED_BYTES) : size);
589
+ const buffer = Buffer.alloc(length);
590
+ let offset = 0;
591
+ const position = Number(size) - length;
592
+ while (offset < length) {
593
+ const { bytesRead } = await handle.read(buffer, offset, length - offset, position + offset);
594
+ if (bytesRead <= 0) throw claimRaceError();
595
+ offset += bytesRead;
596
+ }
597
+ return buffer.toString("utf8");
598
+ }
599
+
600
+ function claimRaceError(): NodeJS.ErrnoException {
601
+ const error = new Error("telemetry install ID claim changed during observation") as NodeJS.ErrnoException;
602
+ error.code = "ECLAIMRACE";
603
+ return error;
604
+ }
605
+
606
+ async function readPublishedInstallIdWhenUnclaimed(filePath: string, claimPath: string): Promise<string> {
607
+ const deadline = performance.now() + INSTALL_ID_CLAIM_WAIT_TIMEOUT_MS;
608
+ while (true) {
609
+ if (performance.now() >= deadline) throw new Error("telemetry install ID claim did not clear");
610
+ const identity = await fs.lstat(filePath, { bigint: true });
611
+ const identityKey = `${identity.dev}:${identity.ino}:${identity.size}:${identity.mtimeNs}`;
612
+ let durability = durableInstallIdPaths.get(filePath);
613
+ if (durability === undefined || durability.identity !== identityKey) {
614
+ const promise = syncDirectory(path.dirname(filePath));
615
+ durability = { identity: identityKey, promise };
616
+ durableInstallIdPaths.set(filePath, durability);
617
+ promise.catch(() => {
618
+ if (durableInstallIdPaths.get(filePath)?.promise === promise) durableInstallIdPaths.delete(filePath);
619
+ });
620
+ }
621
+ await durability.promise;
622
+ try {
623
+ await syncFile(filePath, identity);
624
+ } catch (error) {
625
+ const code = (error as NodeJS.ErrnoException).code;
626
+ if (code === "EACCES") {
627
+ // A valid owner-read-only ID is repaired by the caller after this read.
628
+ } else if (code === "ECLAIMRACE") {
629
+ await Bun.sleep(INSTALL_ID_CLAIM_POLL_INITIAL_MS);
630
+ continue;
631
+ } else throw error;
632
+ }
633
+ let value: string;
634
+ try {
635
+ value = await readPublishedInstallId(filePath);
636
+ } catch (error) {
637
+ if ((error as NodeJS.ErrnoException).code === "ECLAIMRACE") {
638
+ if (performance.now() >= deadline) throw new Error("telemetry install ID claim did not clear");
639
+ await Bun.sleep(INSTALL_ID_CLAIM_POLL_INITIAL_MS);
640
+ continue;
641
+ }
642
+ if (!(error instanceof Error) || error.message !== "telemetry install ID is malformed") throw error;
643
+ if (Date.now() - Number((await fs.lstat(filePath, { bigint: true })).mtimeMs) > INSTALL_ID_PARTIAL_GRACE_MS)
644
+ throw error;
645
+ if (performance.now() >= deadline) throw error;
646
+ await Bun.sleep(INSTALL_ID_CLAIM_POLL_INITIAL_MS);
647
+ continue;
648
+ }
649
+ const after = await fs.lstat(filePath, { bigint: true });
650
+ const afterKey = `${after.dev}:${after.ino}:${after.size}:${after.mtimeNs}`;
651
+ if (afterKey !== identityKey) {
652
+ await Bun.sleep(INSTALL_ID_CLAIM_POLL_INITIAL_MS);
653
+ continue;
654
+ }
655
+ try {
656
+ if (await readClaimIdentity(claimPath)) {
657
+ await waitForClaimRelease(claimPath);
658
+ continue;
659
+ }
660
+ const finalIdentity = await fs.lstat(filePath, { bigint: true });
661
+ const finalIdentityKey = `${finalIdentity.dev}:${finalIdentity.ino}:${finalIdentity.size}:${finalIdentity.mtimeNs}`;
662
+ if (finalIdentityKey !== identityKey) {
663
+ await Bun.sleep(INSTALL_ID_CLAIM_POLL_INITIAL_MS);
664
+ continue;
665
+ }
666
+ return value;
667
+ } catch (error) {
668
+ const code = (error as NodeJS.ErrnoException).code;
669
+ if (code !== "ECLAIMRACE" && code !== "ECLAIMLOST") throw error;
670
+ if (performance.now() >= deadline) throw new Error("telemetry install ID claim did not clear");
671
+ }
672
+ }
673
+ }
674
+
675
+ async function readWinnerUnderOwnedClaim(filePath: string, claimPath: string, token: string): Promise<string> {
676
+ const deadline = performance.now() + INSTALL_ID_CLAIM_WAIT_TIMEOUT_MS;
677
+ while (performance.now() < deadline) {
678
+ const before = await fs.lstat(filePath, { bigint: true });
679
+ let value: string;
680
+ try {
681
+ value = await readPublishedInstallIdWithPartialGrace(filePath, deadline);
682
+ } catch (error) {
683
+ if ((error as NodeJS.ErrnoException).code !== "ECLAIMRACE") throw error;
684
+ await Bun.sleep(INSTALL_ID_CLAIM_POLL_INITIAL_MS);
685
+ continue;
686
+ }
687
+ try {
688
+ await syncFile(filePath, before);
689
+ } catch (error) {
690
+ if ((error as NodeJS.ErrnoException).code !== "ECLAIMRACE") throw error;
691
+ await Bun.sleep(INSTALL_ID_CLAIM_POLL_INITIAL_MS);
692
+ continue;
693
+ }
694
+ await syncDirectory(path.dirname(filePath));
695
+ const after = await fs.lstat(filePath, { bigint: true });
696
+ if (
697
+ before.dev === after.dev &&
698
+ before.ino === after.ino &&
699
+ before.size === after.size &&
700
+ before.mtimeNs === after.mtimeNs
701
+ ) {
702
+ await assertClaimOwned(claimPath, token, "publishing");
703
+ if (!(await chmodWinner(filePath, after))) {
704
+ await Bun.sleep(INSTALL_ID_CLAIM_POLL_INITIAL_MS);
705
+ continue;
706
+ }
707
+ const permissionChecked = await fs.lstat(filePath, { bigint: true });
708
+ if (
709
+ permissionChecked.dev !== after.dev ||
710
+ permissionChecked.ino !== after.ino ||
711
+ permissionChecked.size !== after.size ||
712
+ permissionChecked.mtimeNs !== after.mtimeNs
713
+ )
714
+ continue;
715
+ await assertClaimOwned(claimPath, token, "publishing");
716
+ return value;
717
+ }
718
+ await Bun.sleep(INSTALL_ID_CLAIM_POLL_INITIAL_MS);
719
+ }
720
+ throw new Error("telemetry install ID claim did not clear");
721
+ }
722
+
723
+ async function readWinnerAfterCollision(filePath: string, claimPath: string, token: string): Promise<string> {
724
+ try {
725
+ return await readWinnerUnderOwnedClaim(filePath, claimPath, token);
726
+ } catch (error) {
727
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") throw claimRaceError();
728
+ throw error;
729
+ }
730
+ }
731
+
732
+ async function readPublishedInstallIdWithPartialGrace(filePath: string, deadline: number): Promise<string> {
733
+ while (true) {
734
+ try {
735
+ return await readPublishedInstallId(filePath);
736
+ } catch (error) {
737
+ if (!(error instanceof Error) || error.message !== "telemetry install ID is malformed") throw error;
738
+ const stat = await fs.lstat(filePath, { bigint: true });
739
+ if (Date.now() - Number(stat.mtimeMs) > INSTALL_ID_PARTIAL_GRACE_MS || performance.now() >= deadline)
740
+ throw error;
741
+ await Bun.sleep(INSTALL_ID_CLAIM_POLL_INITIAL_MS);
742
+ }
743
+ }
744
+ }
745
+
746
+ async function refreshClaimLease(claimPath: string, token: string): Promise<void> {
747
+ let handle: fs.FileHandle | undefined;
748
+ try {
749
+ const named = await fs.lstat(claimPath, { bigint: true });
750
+ handle = await openClaimFile(claimPath, INSTALL_ID_WRITE_FLAGS);
751
+ const opened = await handle.stat({ bigint: true });
752
+ if (named.dev !== opened.dev || named.ino !== opened.ino) return;
753
+ const content = await readClaimHandleBounded(handle, opened.size);
754
+ const claim = parseClaim(content);
755
+ if (claim.token !== token || claim.state === undefined) return;
756
+ const contentDigest = hashClaimBytes(content);
757
+ await handle.close();
758
+ handle = await openClaimFile(claimPath, INSTALL_ID_WRITE_FLAGS);
759
+ const reopened = await handle.stat({ bigint: true });
760
+ if (
761
+ reopened.dev !== opened.dev ||
762
+ reopened.ino !== opened.ino ||
763
+ reopened.size !== opened.size ||
764
+ reopened.mtimeNs !== opened.mtimeNs ||
765
+ hashClaimBytes(await readClaimHandleBounded(handle, reopened.size)) !== contentDigest
766
+ )
767
+ throw claimLostError();
768
+ const record = Buffer.from(serializeClaim(token, claim.state, Date.now() + INSTALL_ID_CLAIM_LEASE_MS));
769
+ await writeBoundedClaimRecord(handle, record, reopened.size);
770
+ await handle.sync();
771
+ await handle.utimes(new Date(), new Date());
772
+ } catch (error) {
773
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return;
774
+ throw error;
110
775
  } finally {
776
+ await handle?.close().catch(() => undefined);
777
+ }
778
+ }
779
+
780
+ async function assertClaimOwned(
781
+ claimPath: string,
782
+ token: string,
783
+ state: Exclude<ClaimState, undefined>,
784
+ ): Promise<void> {
785
+ const claim = await readClaimIdentity(claimPath);
786
+ if (claim?.token === token && claim.state === state) return;
787
+ const error = new Error("telemetry install ID claim ownership was lost") as NodeJS.ErrnoException;
788
+ error.code = "ECLAIMLOST";
789
+ throw error;
790
+ }
791
+
792
+ async function transitionClaimCommitted(claimPath: string, token: string): Promise<void> {
793
+ let handle: fs.FileHandle | undefined;
794
+ try {
795
+ const before = await fs.lstat(claimPath, { bigint: true });
796
+ handle = await openClaimFile(claimPath, INSTALL_ID_WRITE_FLAGS);
797
+ const opened = await handle.stat({ bigint: true });
798
+ if (opened.dev !== before.dev || opened.ino !== before.ino) throw new Error("telemetry install ID claim changed");
799
+ const content = await readClaimHandleBounded(handle, opened.size);
800
+ const parsed = parseClaim(content);
801
+ if (parsed.token !== token || parsed.state !== "publishing")
802
+ throw new Error("telemetry install ID claim changed");
803
+ const contentDigest = hashClaimBytes(content);
111
804
  await handle.close();
805
+ handle = await openClaimFile(claimPath, INSTALL_ID_WRITE_FLAGS);
806
+ const reopened = await handle.stat({ bigint: true });
807
+ if (
808
+ reopened.dev !== opened.dev ||
809
+ reopened.ino !== opened.ino ||
810
+ reopened.size !== opened.size ||
811
+ reopened.mtimeNs !== opened.mtimeNs ||
812
+ hashClaimBytes(await readClaimHandleBounded(handle, reopened.size)) !== contentDigest
813
+ )
814
+ throw new Error("telemetry install ID claim changed");
815
+ const record = Buffer.from(serializeClaim(token, "committed", Date.now() + INSTALL_ID_CLAIM_LEASE_MS));
816
+ await writeBoundedClaimRecord(handle, record, reopened.size);
817
+ } catch (error) {
818
+ const code = (error as NodeJS.ErrnoException).code;
819
+ if (code === "ENOENT" || code === "ECLAIMRACE") throw claimLostError();
820
+ throw error;
821
+ } finally {
822
+ await handle?.close();
823
+ }
824
+ await assertClaimOwned(claimPath, token, "committed");
825
+ }
826
+
827
+ async function syncClaimDurably(claimPath: string, token: string): Promise<void> {
828
+ const handle = await openClaimFile(claimPath, INSTALL_ID_WRITE_FLAGS);
829
+ try {
830
+ const before = await handle.stat({ bigint: true });
831
+ if (!before.isFile() || parseClaim(await readClaimHandleBounded(handle, before.size)).token !== token)
832
+ throw claimLostError();
833
+ await handle.sync();
834
+ } finally {
835
+ await handle.close();
836
+ }
837
+ await assertClaimOwned(claimPath, token, "committed");
838
+ }
839
+
840
+ async function writeClaimRecord(handle: fs.FileHandle, record: Uint8Array, position: number): Promise<void> {
841
+ let offset = 0;
842
+ while (offset < record.byteLength) {
843
+ const { bytesWritten } = await handle.write(record, offset, record.byteLength - offset, position + offset);
844
+ if (bytesWritten <= 0) throw new Error("telemetry claim generation write made no progress");
845
+ offset += bytesWritten;
846
+ }
847
+ }
848
+
849
+ async function writeBoundedClaimRecord(handle: fs.FileHandle, record: Uint8Array, currentSize: bigint): Promise<void> {
850
+ if (currentSize + BigInt(record.byteLength) <= BigInt(CLAIM_MAX_SERIALIZED_BYTES)) {
851
+ await writeClaimRecord(handle, record, Number(currentSize));
852
+ return;
853
+ }
854
+ await writeClaimRecord(handle, record, 0);
855
+ await handle.sync();
856
+ await handle.truncate(record.byteLength);
857
+ await handle.sync();
858
+ }
859
+
860
+ function claimLostError(): NodeJS.ErrnoException {
861
+ const error = new Error("telemetry install ID claim ownership was lost") as NodeJS.ErrnoException;
862
+ error.code = "ECLAIMLOST";
863
+ return error;
864
+ }
865
+
866
+ function parseClaim(content: string): { token: string; state: ClaimState; expiresAt: number | undefined } {
867
+ const lines = content.split("\n");
868
+ if (lines.at(-1) === "") lines.pop();
869
+ else lines.pop();
870
+ for (const line of lines.reverse()) {
871
+ const match = /^([^|\n]+)\|(publishing|committed)(?:\|(\d+))?$/.exec(line);
872
+ if (match)
873
+ return {
874
+ token: match[1],
875
+ state: match[2] as Exclude<ClaimState, undefined>,
876
+ expiresAt: match[3] === undefined ? undefined : Number(match[3]),
877
+ };
878
+ }
879
+ const [token, stateOrExpiry, expiryValue] = content.split("\n", 3);
880
+ const state = stateOrExpiry === "publishing" || stateOrExpiry === "committed" ? stateOrExpiry : undefined;
881
+ const expiry = state === undefined ? stateOrExpiry : expiryValue;
882
+ const expiresAt = expiry === undefined ? undefined : Number(expiry);
883
+ return { token, state, expiresAt: Number.isFinite(expiresAt) ? expiresAt : undefined };
884
+ }
885
+
886
+ function serializeClaim(token: string, state: Exclude<ClaimState, undefined>, expiresAt?: number): string {
887
+ return `${token}|${state}${expiresAt === undefined ? "" : `|${expiresAt}`}\n`;
888
+ }
889
+
890
+ async function waitForClaimRelease(
891
+ claimPath: string,
892
+ expectedClaim: ClaimIdentity | undefined = undefined,
893
+ deadline = performance.now() + INSTALL_ID_CLAIM_WAIT_TIMEOUT_MS,
894
+ ): Promise<void> {
895
+ while (performance.now() < deadline) {
896
+ try {
897
+ const stat = await fs.stat(claimPath, { bigint: true });
898
+ const claim = await readClaimIdentity(claimPath);
899
+ if (
900
+ expectedClaim !== undefined &&
901
+ (claim === undefined ||
902
+ claim.dev !== expectedClaim.dev ||
903
+ claim.ino !== expectedClaim.ino ||
904
+ claim.token !== expectedClaim.token ||
905
+ claim.size !== expectedClaim.size ||
906
+ claim.digest !== expectedClaim.digest)
907
+ )
908
+ throw claimLostError();
909
+ const publishingExpired =
910
+ claim?.state === "publishing" &&
911
+ ((claim.expiresAt !== undefined &&
912
+ claim.expiresAt <= Date.now() - INSTALL_ID_CLAIM_LEASE_MS &&
913
+ Date.now() - claim.mtimeMs > INSTALL_ID_CLAIM_TIMEOUT_MS) ||
914
+ (claim.expiresAt === undefined && Date.now() - claim.mtimeMs > INSTALL_ID_CLAIM_TIMEOUT_MS));
915
+ const committedStale =
916
+ claim?.state === "committed" &&
917
+ ((claim.expiresAt !== undefined && claim.expiresAt <= Date.now()) ||
918
+ (claim.expiresAt === undefined && Date.now() - claim.mtimeMs > INSTALL_ID_CLAIM_LEASE_MS));
919
+ const malformedStale =
920
+ claim !== undefined &&
921
+ claim.state === undefined &&
922
+ stat.size === 0n &&
923
+ Date.now() - claim.mtimeMs > INSTALL_ID_CLAIM_TIMEOUT_MS;
924
+ if (claim !== undefined && (publishingExpired || committedStale || malformedStale)) {
925
+ await reclaimStaleClaim(claimPath, stat, claim);
926
+ await Bun.sleep(INSTALL_ID_CLAIM_POLL_INITIAL_MS);
927
+ continue;
928
+ }
929
+ } catch (error) {
930
+ const code = (error as NodeJS.ErrnoException).code;
931
+ if (code === "ENOENT") return;
932
+ if (code === "ECLAIMRACE") {
933
+ await Bun.sleep(INSTALL_ID_CLAIM_POLL_INITIAL_MS);
934
+ continue;
935
+ }
936
+ throw error;
937
+ }
938
+ await Bun.sleep(INSTALL_ID_CLAIM_POLL_INITIAL_MS);
939
+ }
940
+ throw new Error("telemetry install ID claim did not clear");
941
+ }
942
+
943
+ async function convergeAfterClaim(filePath: string): Promise<string> {
944
+ const deadline = performance.now() + INSTALL_ID_CLAIM_WAIT_TIMEOUT_MS;
945
+ while (performance.now() < deadline) {
946
+ const claimPath = `${filePath}.lock`;
947
+ let observed: ClaimIdentity | undefined;
948
+ try {
949
+ observed = await readClaimIdentity(claimPath);
950
+ const expected = observed?.state === "publishing" ? observed : undefined;
951
+ await waitForClaimRelease(claimPath, expected, deadline);
952
+ } catch (error) {
953
+ const code = (error as NodeJS.ErrnoException).code;
954
+ if (code !== "ECLAIMLOST" && code !== "ECLAIMRACE") throw error;
955
+ await Bun.sleep(INSTALL_ID_CLAIM_POLL_INITIAL_MS);
956
+ continue;
957
+ }
958
+ try {
959
+ return await readExistingInstallId(filePath);
960
+ } catch (error) {
961
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
962
+ }
963
+ try {
964
+ return await publishPortably(filePath, randomUUID());
965
+ } catch (error) {
966
+ const code = (error as NodeJS.ErrnoException).code;
967
+ if (code !== "ECLAIM" && code !== "EEXIST") throw error;
968
+ }
969
+ }
970
+ throw new Error("telemetry install ID claim did not clear");
971
+ }
972
+
973
+ async function reclaimStaleClaim(claimPath: string, stat: BigIntStats, claim: ClaimIdentity): Promise<void> {
974
+ if (!stat.isFile()) return;
975
+ if (claim.state === "publishing") await syncDirectory(path.dirname(claimPath));
976
+ const currentClaim = await readClaimIdentity(claimPath);
977
+ if (
978
+ currentClaim === undefined ||
979
+ currentClaim.dev !== claim.dev ||
980
+ currentClaim.ino !== claim.ino ||
981
+ currentClaim.token !== claim.token ||
982
+ currentClaim.size !== claim.size ||
983
+ currentClaim.digest !== claim.digest ||
984
+ currentClaim.state !== claim.state ||
985
+ currentClaim.expiresAt !== claim.expiresAt ||
986
+ ((currentClaim.state === "publishing" || currentClaim.state === "committed") &&
987
+ (currentClaim.expiresAt === undefined
988
+ ? Date.now() - currentClaim.mtimeMs <= INSTALL_ID_CLAIM_TIMEOUT_MS
989
+ : currentClaim.expiresAt > Date.now()))
990
+ )
991
+ return;
992
+ const snapshot = await readClaimSnapshot(claimPath);
993
+ const finalClaim = parseClaim(snapshot.content);
994
+ if (
995
+ finalClaim.token !== claim.token ||
996
+ finalClaim.state !== claim.state ||
997
+ ((finalClaim.state === "publishing" || finalClaim.state === "committed") &&
998
+ finalClaim.expiresAt !== undefined &&
999
+ finalClaim.expiresAt > Date.now())
1000
+ )
1001
+ return;
1002
+ const current = snapshot.stat;
1003
+ const currentMtimeMs = Number(current.mtimeMs);
1004
+ if (
1005
+ (finalClaim.state === "publishing" || finalClaim.state === "committed") &&
1006
+ (finalClaim.expiresAt === undefined
1007
+ ? Date.now() - currentMtimeMs <= INSTALL_ID_CLAIM_TIMEOUT_MS
1008
+ : finalClaim.expiresAt > Date.now())
1009
+ )
1010
+ return;
1011
+ if (current.dev !== stat.dev || current.ino !== stat.ino) return;
1012
+ const cleanupDigest = snapshot.digest;
1013
+ const cleanupKey = `${claimPath}:${current.dev}:${current.ino}:${current.nlink}:${current.size}:${current.mtimeNs}:${cleanupDigest}:${claim.token}:${claim.state}:${claim.expiresAt ?? ""}`;
1014
+ if (scheduledClaimCleanups.has(cleanupKey)) return;
1015
+ scheduledClaimCleanups.add(cleanupKey);
1016
+ const releaseTimer = setTimeout(() => scheduledClaimCleanups.delete(cleanupKey), INSTALL_ID_CLAIM_TIMEOUT_MS);
1017
+ releaseTimer.unref();
1018
+ scheduleExactDetachedCleanup(claimPath, {
1019
+ dev: current.dev,
1020
+ ino: current.ino,
1021
+ nlink: current.nlink,
1022
+ size: current.size,
1023
+ mtimeNs: current.mtimeNs,
1024
+ sha256: cleanupDigest,
1025
+ allowHardLink: true,
1026
+ quarantineName: `.${path.basename(claimPath)}.${randomUUID()}.quarantine`,
1027
+ });
1028
+ }
1029
+
1030
+ async function removeOwnedClaim(claimPath: string, token: string): Promise<void> {
1031
+ try {
1032
+ const snapshot = await readClaimSnapshot(claimPath);
1033
+ if (parseClaim(snapshot.content).token !== token) return;
1034
+ scheduleExactDetachedCleanup(claimPath, {
1035
+ dev: snapshot.stat.dev,
1036
+ ino: snapshot.stat.ino,
1037
+ nlink: snapshot.stat.nlink,
1038
+ size: snapshot.stat.size,
1039
+ mtimeNs: snapshot.stat.mtimeNs,
1040
+ sha256: snapshot.digest,
1041
+ allowHardLink: true,
1042
+ quarantineName: `.${path.basename(claimPath)}.${randomUUID()}.quarantine`,
1043
+ });
1044
+ } catch (error) {
1045
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
1046
+ }
1047
+ }
1048
+
1049
+ function scheduleOwnedClaimCleanup(claimPath: string, token: string): void {
1050
+ const cleanupKey = `${claimPath}:${token}`;
1051
+ if (scheduledClaimCleanups.has(cleanupKey)) return;
1052
+ scheduledClaimCleanups.add(cleanupKey);
1053
+ void removeOwnedClaim(claimPath, token).catch(() => undefined);
1054
+ const releaseTimer = setTimeout(() => scheduledClaimCleanups.delete(cleanupKey), INSTALL_ID_CLAIM_TIMEOUT_MS);
1055
+ releaseTimer.unref();
1056
+ }
1057
+
1058
+ async function publishWithClaim(filePath: string, installId: string): Promise<string> {
1059
+ const claimPath = `${filePath}.lock`;
1060
+ const token = randomUUID();
1061
+ const temporaryPath = `${filePath}.${randomUUID()}.tmp`;
1062
+ const claimTemporaryPath = `${claimPath}.${randomUUID()}.tmp`;
1063
+ let ownsClaim = false;
1064
+ let publishedFinal = false;
1065
+ let committed = false;
1066
+ let leaseTimer: NodeJS.Timeout | undefined;
1067
+ let heartbeat: Promise<void> = Promise.resolve();
1068
+ let heartbeatFailure: unknown;
1069
+ let heartbeatStopped = false;
1070
+ let claimTemporaryIdentity: BigIntStats | undefined;
1071
+ let claimTemporaryContent: string | undefined;
1072
+ let claimTemporaryCleanupScheduled = false;
1073
+ let temporaryIdentity: BigIntStats | undefined;
1074
+ let temporaryCleanupScheduled = false;
1075
+ let claim: fs.FileHandle;
1076
+ try {
1077
+ try {
1078
+ claim = await fs.open(claimTemporaryPath, "wx", 0o600);
1079
+ } catch (error) {
1080
+ if ((error as NodeJS.ErrnoException).code === "EEXIST") {
1081
+ const busy = new Error("telemetry install ID claim is busy") as NodeJS.ErrnoException;
1082
+ busy.code = "ECLAIM";
1083
+ throw busy;
1084
+ }
1085
+ throw error;
1086
+ }
1087
+ try {
1088
+ claimTemporaryContent = serializeClaim(token, "publishing", Date.now() + INSTALL_ID_CLAIM_LEASE_MS);
1089
+ await claim.writeFile(claimTemporaryContent, "utf8");
1090
+ await claim.sync();
1091
+ claimTemporaryIdentity = await claim.stat({ bigint: true });
1092
+ } finally {
1093
+ await claim.close();
1094
+ }
1095
+ const claimPublication = await natives.renameNoReplacePathAsync(claimTemporaryPath, claimPath);
1096
+ if (!claimPublication.ok) {
1097
+ if (claimPublication.code === "destination_exists" || claimPublication.reason === "destination_exists") {
1098
+ const busy = new Error("telemetry install ID claim is busy") as NodeJS.ErrnoException;
1099
+ busy.code = "ECLAIM";
1100
+ throw busy;
1101
+ }
1102
+ throw new Error(`telemetry install ID claim publication failed: ${claimPublication.reason}`);
1103
+ }
1104
+ claimTemporaryIdentity = undefined;
1105
+ ownsClaim = (await readClaimIdentity(claimPath))?.token === token;
1106
+ if (!ownsClaim) throw new Error("telemetry install ID claim changed");
1107
+ const scheduleHeartbeat = (): void => {
1108
+ if (heartbeatStopped) return;
1109
+ leaseTimer = setTimeout(() => {
1110
+ leaseTimer = undefined;
1111
+ heartbeat = refreshClaimLease(claimPath, token)
1112
+ .catch(error => {
1113
+ heartbeatFailure = error;
1114
+ })
1115
+ .finally(scheduleHeartbeat);
1116
+ }, INSTALL_ID_HEARTBEAT_INTERVAL_MS);
1117
+ leaseTimer.unref();
1118
+ };
1119
+ scheduleHeartbeat();
1120
+ try {
1121
+ return await readWinnerUnderOwnedClaim(filePath, claimPath, token);
1122
+ } catch (error) {
1123
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
1124
+ }
1125
+ let handle: fs.FileHandle;
1126
+ try {
1127
+ handle = await fs.open(temporaryPath, "wx", 0o600);
1128
+ } catch (error) {
1129
+ if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
1130
+ return await readWinnerAfterCollision(filePath, claimPath, token);
1131
+ }
1132
+ try {
1133
+ await handle.writeFile(`${installId}\n`, "utf8");
1134
+ await handle.sync();
1135
+ temporaryIdentity = await handle.stat({ bigint: true });
1136
+ } finally {
1137
+ await handle.close().catch(() => undefined);
1138
+ }
1139
+ heartbeatStopped = true;
1140
+ if (leaseTimer !== undefined) clearTimeout(leaseTimer);
1141
+ await heartbeat;
1142
+ if (heartbeatFailure !== undefined) throw heartbeatFailure;
1143
+ await refreshClaimLease(claimPath, token);
1144
+ heartbeatStopped = false;
1145
+ scheduleHeartbeat();
1146
+ await assertClaimOwned(claimPath, token, "publishing");
1147
+ const publication = await natives.renameNoReplacePathAsync(temporaryPath, filePath);
1148
+ if (!publication.ok) {
1149
+ if (publication.code !== "destination_exists" && publication.reason !== "destination_exists")
1150
+ throw new Error(`telemetry install ID publication failed: ${publication.reason}`);
1151
+ return await readWinnerAfterCollision(filePath, claimPath, token);
1152
+ }
1153
+ publishedFinal = true;
1154
+ await assertClaimOwned(claimPath, token, "publishing");
1155
+ await syncDirectory(path.dirname(filePath));
1156
+ heartbeatStopped = true;
1157
+ if (leaseTimer !== undefined) clearTimeout(leaseTimer);
1158
+ await heartbeat;
1159
+ if (heartbeatFailure !== undefined) throw heartbeatFailure;
1160
+ await transitionClaimCommitted(claimPath, token);
1161
+ heartbeatStopped = false;
1162
+ scheduleHeartbeat();
1163
+ await syncClaimDurably(claimPath, token);
1164
+ heartbeatStopped = true;
1165
+ if (leaseTimer !== undefined) clearTimeout(leaseTimer);
1166
+ await heartbeat;
1167
+ if (heartbeatFailure !== undefined) throw heartbeatFailure;
1168
+ committed = true;
1169
+ return installId;
1170
+ } finally {
1171
+ heartbeatStopped = true;
1172
+ if (leaseTimer !== undefined) clearTimeout(leaseTimer);
1173
+ await heartbeat;
1174
+ if (claimTemporaryIdentity === undefined && claimTemporaryContent !== undefined) {
1175
+ const failedIdentity = await fs.lstat(claimTemporaryPath, { bigint: true }).catch(() => undefined);
1176
+ if (failedIdentity !== undefined) {
1177
+ const failedContent = (await readStagedContentBounded(claimTemporaryPath)) ?? "";
1178
+ scheduleExactStagedCleanup(claimTemporaryPath, failedIdentity, failedContent);
1179
+ claimTemporaryCleanupScheduled = true;
1180
+ claimTemporaryIdentity = failedIdentity;
1181
+ }
1182
+ }
1183
+ if (temporaryIdentity === undefined) {
1184
+ const failedIdentity = await fs.lstat(temporaryPath, { bigint: true }).catch(() => undefined);
1185
+ if (failedIdentity !== undefined) {
1186
+ const failedContent = (await readStagedContentBounded(temporaryPath)) ?? "";
1187
+ scheduleExactStagedCleanup(temporaryPath, failedIdentity, failedContent);
1188
+ temporaryCleanupScheduled = true;
1189
+ temporaryIdentity = failedIdentity;
1190
+ }
1191
+ }
1192
+ if (
1193
+ claimTemporaryIdentity !== undefined &&
1194
+ claimTemporaryContent !== undefined &&
1195
+ !claimTemporaryCleanupScheduled
1196
+ ) {
1197
+ const currentIdentity = await fs.lstat(claimTemporaryPath, { bigint: true }).catch(() => undefined);
1198
+ if (currentIdentity !== undefined) {
1199
+ const currentContent = (await readStagedContentBounded(claimTemporaryPath)) ?? claimTemporaryContent;
1200
+ scheduleExactStagedCleanup(claimTemporaryPath, currentIdentity, currentContent);
1201
+ claimTemporaryCleanupScheduled = true;
1202
+ }
1203
+ }
1204
+ if (temporaryIdentity !== undefined && !temporaryCleanupScheduled)
1205
+ scheduleExactStagedCleanup(temporaryPath, temporaryIdentity, `${installId}\n`);
1206
+ if (ownsClaim && (!publishedFinal || committed)) scheduleOwnedClaimCleanup(claimPath, token);
1207
+ }
1208
+ }
1209
+
1210
+ async function publishPortably(filePath: string, installId: string): Promise<string> {
1211
+ try {
1212
+ await publishNewInstallId(filePath, installId);
1213
+ return installId;
1214
+ } catch (error) {
1215
+ if ((error as NodeJS.ErrnoException).code !== "EUNSUPPORTED") throw error;
1216
+ return publishWithClaim(filePath, installId);
112
1217
  }
113
1218
  }
114
1219
 
@@ -116,10 +1221,24 @@ async function writeNewInstallId(filePath: string, installId: string): Promise<v
116
1221
  export async function getTelemetryInstallId(
117
1222
  filePath = getTrustedAgentFile(TELEMETRY_INSTALL_ID_FILE),
118
1223
  ): Promise<string> {
1224
+ const deadline = performance.now() + INSTALL_ID_CLAIM_WAIT_TIMEOUT_MS;
1225
+ while (true) {
1226
+ try {
1227
+ return await getTelemetryInstallIdOnce(filePath);
1228
+ } catch (error) {
1229
+ const code = (error as NodeJS.ErrnoException).code;
1230
+ if (code !== "ECLAIMRACE" && code !== "ECLAIMLOST") throw error;
1231
+ if (performance.now() >= deadline) throw new Error("telemetry install ID claim did not clear");
1232
+ await Bun.sleep(INSTALL_ID_CLAIM_POLL_INITIAL_MS);
1233
+ }
1234
+ }
1235
+ }
1236
+
1237
+ async function getTelemetryInstallIdOnce(filePath: string): Promise<string> {
119
1238
  try {
120
- const existing = (await Bun.file(filePath).text()).trim();
1239
+ const existing = await readExistingInstallId(filePath);
121
1240
  if (UUID_V4.test(existing)) {
122
- await fs.chmod(filePath, 0o600);
1241
+ await tightenInstallIdPermissions(filePath, existing);
123
1242
  return existing;
124
1243
  }
125
1244
  throw new Error("telemetry install ID is malformed");
@@ -130,13 +1249,20 @@ export async function getTelemetryInstallId(
130
1249
  await fs.mkdir(path.dirname(filePath), { recursive: true, mode: 0o700 });
131
1250
  const generated = randomUUID();
132
1251
  try {
133
- await writeNewInstallId(filePath, generated);
134
- return generated;
1252
+ const published = await publishPortably(filePath, generated);
1253
+ await tightenInstallIdPermissions(filePath, published);
1254
+ return published;
135
1255
  } catch (error) {
136
- if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
137
- const existing = (await Bun.file(filePath).text()).trim();
138
- if (!UUID_V4.test(existing)) throw new Error("telemetry install ID is malformed");
139
- await fs.chmod(filePath, 0o600);
140
- return existing;
1256
+ if ((error as NodeJS.ErrnoException).code === "EEXIST") {
1257
+ const existing = await readExistingInstallId(filePath);
1258
+ await tightenInstallIdPermissions(filePath, existing);
1259
+ return existing;
1260
+ }
1261
+ if ((error as NodeJS.ErrnoException).code === "ECLAIM") {
1262
+ const existing = await convergeAfterClaim(filePath);
1263
+ await tightenInstallIdPermissions(filePath, existing);
1264
+ return existing;
1265
+ }
1266
+ throw error;
141
1267
  }
142
1268
  }