Skip to content

fix(export): make export clientId collision-safe to prevent concurrent lost-update#10458

Open
davidfirst wants to merge 1 commit into
masterfrom
fix-concurrent-export-clientid-collision
Open

fix(export): make export clientId collision-safe to prevent concurrent lost-update#10458
davidfirst wants to merge 1 commit into
masterfrom
fix-concurrent-export-clientid-collision

Conversation

@davidfirst

Copy link
Copy Markdown
Member

Problem

export's clientId is both the pending-dir name and the cross-client export lock — export-validate's waitIfNeeded queue sorts pending-dir names and lets only the first proceed to validate+persist. It was generated as Date.now().toString(), which isn't collision-safe.

When two exports hit the same remote within the same millisecond (e.g. concurrent CI runners pushing the same lane), they get the same clientId, share one pending-dir, collapse the queue to a single entry, and both validate against the pre-persist state — silently losing one runner's update (no divergence detected, no rebase).

This surfaced as an intermittently-failing e2e test (ci-commands.e2e.ts → "concurrent runners snapping the SAME component"): both runners exported cleanly and one snap was dropped from the lane. The outcome depended purely on OS scheduling of the two processes.

Fix

Append a random suffix to the generated clientId so same-millisecond exports can't collide. The timestamp prefix is kept so the sorted queue still roughly preserves arrival order. --resume is unaffected (it echoes back a user-supplied id; clientId is never parsed numerically).

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Fix export clientId collisions by adding random suffix for concurrent runs
🐞 Bug fix 🕐 10-20 Minutes

Grey Divider

Description

• Make export clientId unique across same-millisecond concurrent exports.
• Prevent pending-dir/lock collisions that can silently drop one runner’s export.
• Preserve queue ordering by keeping timestamp prefix in the clientId.
Diagram

graph TD
  A["Export CLI"] --> B["export.main.runtime.ts"] --> C["clientId: now-rand"] --> D[("Remote pending-dir")] --> E["export-validate.waitIfNeeded"] --> F["validateRemotes"] --> G["persistRemotes"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use UUID v4 (crypto.randomUUID) as clientId
  • ➕ Stronger uniqueness guarantees with well-known semantics
  • ➕ Avoids needing to reason about random-string implementation quality
  • ➖ Loses natural lexicographic time ordering unless prefixed with a timestamp (still solvable)
  • ➖ May require runtime/polyfill considerations depending on Node target
2. Use a monotonic timestamp (hrtime) + pid
  • ➕ Preserves strict ordering and uniqueness within a host process
  • ➕ Easy to debug (contains pid/time)
  • ➖ Doesn’t guarantee uniqueness across multiple machines/containers hitting same remote
  • ➖ More moving parts than timestamp+random for distributed CI
3. Server-side lock/queue not based on directory names
  • ➕ Avoids relying on client-generated identifiers for correctness
  • ➕ Could enforce fairness and correctness centrally
  • ➖ Larger architectural change; higher risk and more code to maintain
  • ➖ Requires server protocol changes and migrations for existing pending-dir semantics

Recommendation: The chosen approach (timestamp prefix + random suffix) is the best minimal-risk fix: it preserves the existing lexicographic queue behavior while eliminating same-millisecond collisions that break locking. A full server-side lock redesign would be more robust but is not warranted for this targeted concurrency bug.

Files changed (1) +9 / -1

Bug fix (1) +9 / -1
export.main.runtime.tsGenerate collision-safe export clientId using timestamp + random suffix +9/-1

Generate collision-safe export clientId using timestamp + random suffix

• Imports 'generateRandomStr' and changes export clientId generation from 'Date.now()' to a timestamp-prefixed, random-suffixed identifier. Adds inline documentation explaining how clientId doubles as the remote pending-dir name and the export-validate cross-client lock, and why uniqueness is required to prevent lost updates during concurrent exports.

scopes/scope/export/export.main.runtime.ts

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 26, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. PutRoute returns 500 ✓ Resolved 🐞 Bug ≡ Correctness
Description
PutRoute now throws on missing/invalid push-options instead of responding with 400, and the
Express error wrapper defaults thrown errors to status 500. This changes a client input error into a
server error and can break clients relying on 400s (and pollutes server error logs).
Code

scopes/scope/scope/routes/put.route.ts[R21-22]

+      if (!pushOptionsStr) throw new Error('http is missing the push-options header');
+      const pushOptions = JSON.parse(pushOptionsStr as string);
Evidence
The route now throws and unconditionally parses JSON; the express middleware wrapper catches the
thrown error and forces status 500 unless an explicit status is provided.

scopes/scope/scope/routes/put.route.ts[18-30]
scopes/harmony/express/middlewares/error.ts[9-28]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`PutRoute` used to return HTTP 400 for missing/invalid `push-options` headers. The PR replaced this with `throw new Error(...)` and removed the JSON parse guard, which causes the global express error handler to return HTTP 500 (because it defaults `err.status` to 500).
## Issue Context
`@teambit/express` wraps route middlewares with `catchErrors()`, and its `errorHandle()` assigns `err.status = err.status || 500`.
## Fix Focus Areas
- scopes/scope/scope/routes/put.route.ts[20-23]
- scopes/harmony/express/middlewares/error.ts[9-28]
## Suggested fix
- Either:
- Keep the old explicit responses:
- If header missing: `return res.status(400).send(...)`
- If JSON invalid: catch `JSON.parse` and `return res.status(400).send(...)`
- Or (if you want to keep `throw`): throw an error object with `status = 400` (e.g. `const err: any = new Error(...); err.status = 400; throw err;`) and similarly wrap `JSON.parse` errors with `status = 400`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Broad ts-ignore workaround 🐞 Bug ⚙ Maintainability
Description
A new @ts-ignore suppresses a nominal type mismatch when calling
PackageJsonTransformer.applyTransformers, reducing type-safety at that callsite and making future
refactors riskier. Prefer an explicit structural type (or a narrow cast) so the compiler still
checks the required surface area without needing a blanket ignore.
Code

scopes/component/isolator/isolator.main.runtime.ts[R1503-1508]

+    // In dogfooding capsule builds, `@teambit/component.sources` can resolve twice — built from source
+    // for this capsule vs. the published copy `node-modules-linker` pulls in — making `PackageJsonFile`
+    // two identical-but-nominally-distinct classes, so `tsc --declaration` rejects this call (TS2345).
+    // Same shape/version; suppress the seam here, consistent with the other `@ts-ignore`s in this file.
+    // @ts-ignore
await PackageJsonTransformer.applyTransformers(component, packageJson);
Evidence
The PR adds a @ts-ignore directly before the transformer call in the isolator. The transformer
implementation only relies on a small structural API (packageJsonObject and
mergePackageJsonObject), so the nominal-type suppression can be avoided by typing the parameter
structurally instead of disabling checks.

scopes/component/isolator/isolator.main.runtime.ts[1503-1509]
scopes/workspace/modules/node-modules-linker/package-json-transformer.ts[20-44]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`IsolatorMain.populateComponentsFilesToWriteForCapsule()` introduces a `// @ts-ignore` before `PackageJsonTransformer.applyTransformers(component, packageJson);`. This silences the compiler rather than expressing the real intent: the callee only needs a small, structural surface (`packageJsonObject` + `mergePackageJsonObject`).
## Issue Context
`PackageJsonTransformer.applyTransformers()` only reads `packageJson.packageJsonObject` and calls `packageJson.mergePackageJsonObject(...)`, so it does not require a nominal `PackageJsonFile` class identity.
## Fix Focus Areas
- scopes/component/isolator/isolator.main.runtime.ts[1503-1509]
- scopes/workspace/modules/node-modules-linker/package-json-transformer.ts[20-44]
## Suggested fix approach
1. Introduce a minimal interface type (e.g. `PackageJsonLike`) in `package-json-transformer.ts` capturing only what is used:
- `packageJsonObject: Record<string, any>`
- `mergePackageJsonObject(obj: Record<string, any>): void`
2. Change `applyTransformers(component, packageJson: PackageJsonLike)`.
3. Remove the `@ts-ignore` in the isolator and call `applyTransformers(component, packageJson)` normally (or use a narrow `as unknown as PackageJsonLike` cast if needed).
This keeps the dogfooding workaround while preserving meaningful type-checking.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Unrelated .bitmap churn 🐞 Bug ⚙ Maintainability
Description
This PR changes many component version entries in .bitmap and also updates the CLI reference doc's
stated Bit version, which is unrelated to the clientId-collision fix. This makes the PR noisy and
increases the risk of accidental version metadata changes and merge conflicts.
Code

.bitmap[R19-29]

"api-reference": {
"name": "api-reference",
"scope": "teambit.api-reference",
-        "version": "1.0.1042",
+        "version": "1.0.1041",
"mainFile": "index.ts",
"rootDir": "scopes/api-reference/api-reference"
},
"api-server": {
"name": "api-server",
"scope": "teambit.harmony",
-        "version": "1.0.1072",
+        "version": "1.0.1071",
Evidence
The PR branch shows .bitmap entries with updated (lower) versions and the CLI reference docs
frontmatter indicating a different Bit version string.

.bitmap[19-39]
scopes/harmony/cli-reference/cli-reference.docs.mdx[1-4]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The PR includes large unrelated metadata changes: many version fields in `.bitmap` are decremented, and the CLI reference docs frontmatter version string changes too. This is unrelated to the export `clientId` collision fix and adds review/merge noise.
## Issue Context
`.bitmap` is workspace metadata; unexpected mass edits here are typically generated and should only be included when intentionally updating component versions.
## Fix Focus Areas
- .bitmap[19-50]
- scopes/harmony/cli-reference/cli-reference.docs.mdx[1-4]
## Suggested fix
- If these changes were accidental (e.g. generated by running a command locally): revert `.bitmap` and `cli-reference.docs.mdx` to their pre-PR state.
- If they are intentional: split them into a separate PR (or clearly explain why the version metadata must change as part of this fix) so the functional change can be reviewed/merged independently.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

4. Stale clientId messaging 🐞 Bug ⚙ Maintainability ⭐ New
Description
pushToRemotesCarefully() now generates clientId as ${Date.now()}-<randomHex>, but other code
still documents/errors as if clientId were a pure millisecond timestamp. This mismatch can mislead
future callers/maintainers into adding numeric parsing/validation or misunderstanding collision
scenarios when debugging export failures.
Code

scopes/scope/export/export.main.runtime.ts[691]

+    const clientId = resumeExportId || `${Date.now()}-${crypto.randomBytes(8).toString('hex')}`;
Evidence
The PR changes clientId from a numeric timestamp to a timestamp plus random hex, but the
public-facing PushOptions comment and the ClientIdInUse error message still explicitly describe the
value/collision as millisecond-timestamp-based.

scopes/scope/export/export.main.runtime.ts[680-692]
components/legacy/scope-api/lib/put.ts[11-14]
components/legacy/scope/exceptions/client-id-in-use.ts[3-10]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`clientId` is no longer a pure millisecond timestamp (it now includes a random suffix), but some comments/error text still describe it as "timestamp in ms" / "within the exact same millisecond".

## Issue Context
The PR intentionally changes the `clientId` format to prevent same-millisecond collisions; keeping surrounding documentation accurate helps prevent future code from making incorrect assumptions about `clientId` being numeric.

## Fix Focus Areas
- components/legacy/scope-api/lib/put.ts[11-14]
- components/legacy/scope/exceptions/client-id-in-use.ts[3-10]
- (optional) scopes/scope/export/export.main.runtime.ts[682-691] (ensure comment remains accurate/consistent)

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Previous review results

Review updated until commit 6993ed1

Results up to commit N/A


🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


Action required
1. PutRoute returns 500 ✓ Resolved 🐞 Bug ≡ Correctness
Description
PutRoute now throws on missing/invalid push-options instead of responding with 400, and the
Express error wrapper defaults thrown errors to status 500. This changes a client input error into a
server error and can break clients relying on 400s (and pollutes server error logs).
Code

scopes/scope/scope/routes/put.route.ts[R21-22]

+      if (!pushOptionsStr) throw new Error('http is missing the push-options header');
+      const pushOptions = JSON.parse(pushOptionsStr as string);
Evidence
The route now throws and unconditionally parses JSON; the express middleware wrapper catches the
thrown error and forces status 500 unless an explicit status is provided.

scopes/scope/scope/routes/put.route.ts[18-30]
scopes/harmony/express/middlewares/error.ts[9-28]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`PutRoute` used to return HTTP 400 for missing/invalid `push-options` headers. The PR replaced this with `throw new Error(...)` and removed the JSON parse guard, which causes the global express error handler to return HTTP 500 (because it defaults `err.status` to 500).
## Issue Context
`@teambit/express` wraps route middlewares with `catchErrors()`, and its `errorHandle()` assigns `err.status = err.status || 500`.
## Fix Focus Areas
- scopes/scope/scope/routes/put.route.ts[20-23]
- scopes/harmony/express/middlewares/error.ts[9-28]
## Suggested fix
- Either:
- Keep the old explicit responses:
- If header missing: `return res.status(400).send(...)`
- If JSON invalid: catch `JSON.parse` and `return res.status(400).send(...)`
- Or (if you want to keep `throw`): throw an error object with `status = 400` (e.g. `const err: any = new Error(...); err.status = 400; throw err;`) and similarly wrap `JSON.parse` errors with `status = 400`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended
2. Broad ts-ignore workaround 🐞 Bug ⚙ Maintainability
Description
A new @ts-ignore suppresses a nominal type mismatch when calling
PackageJsonTransformer.applyTransformers, reducing type-safety at that callsite and making future
refactors riskier. Prefer an explicit structural type (or a narrow cast) so the compiler still
checks the required surface area without needing a blanket ignore.
Code

scopes/component/isolator/isolator.main.runtime.ts[R1503-1508]

+    // In dogfooding capsule builds, `@teambit/component.sources` can resolve twice — built from source
+    // for this capsule vs. the published copy `node-modules-linker` pulls in — making `PackageJsonFile`
+    // two identical-but-nominally-distinct classes, so `tsc --declaration` rejects this call (TS2345).
+    // Same shape/version; suppress the seam here, consistent with the other `@ts-ignore`s in this file.
+    // @ts-ignore
await PackageJsonTransformer.applyTransformers(component, packageJson);
Evidence
The PR adds a @ts-ignore directly before the transformer call in the isolator. The transformer
implementation only relies on a small structural API (packageJsonObject and
mergePackageJsonObject), so the nominal-type suppression can be avoided by typing the parameter
structurally instead of disabling checks.

scopes/component/isolator/isolator.main.runtime.ts[1503-1509]
scopes/workspace/modules/node-modules-linker/package-json-transformer.ts[20-44]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`IsolatorMain.populateComponentsFilesToWriteForCapsule()` introduces a `// @ts-ignore` before `PackageJsonTransformer.applyTransformers(component, packageJson);`. This silences the compiler rather than expressing the real intent: the callee only needs a small, structural surface (`packageJsonObject` + `mergePackageJsonObject`).
## Issue Context
`PackageJsonTransformer.applyTransformers()` only reads `packageJson.packageJsonObject` and calls `packageJson.mergePackageJsonObject(...)`, so it does not require a nominal `PackageJsonFile` class identity.
## Fix Focus Areas
- scopes/component/isolator/isolator.main.runtime.ts[1503-1509]
- scopes/workspace/modules/node-modules-linker/package-json-transformer.ts[20-44]
## Suggested fix approach
1. Introduce a minimal interface type (e.g. `PackageJsonLike`) in `package-json-transformer.ts` capturing only what is used:
- `packageJsonObject: Record<string, any>`
- `mergePackageJsonObject(obj: Record<string, any>): void`
2. Change `applyTransformers(component, packageJson: PackageJsonLike)`.
3. Remove the `@ts-ignore` in the isolator and call `applyTransformers(component, packageJson)` normally (or use a narrow `as unknown as PackageJsonLike` cast if needed).
This keeps the dogfooding workaround while preserving meaningful type-checking.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Unrelated .bitmap churn 🐞 Bug ⚙ Maintainability
Description
This PR changes many component version entries in .bitmap and also updates the CLI reference doc's
stated Bit version, which is unrelated to the clientId-collision fix. This makes the PR noisy and
increases the risk of accidental version metadata changes and merge conflicts.
Code

.bitmap[R19-29]

"api-reference": {
"name": "api-reference",
"scope": "teambit.api-reference",
-        "version": "1.0.1042",
+        "version": "1.0.1041",
"mainFile": "index.ts",
"rootDir": "scopes/api-reference/api-reference"
},
"api-server": {
"name": "api-server",
"scope": "teambit.harmony",
-        "version": "1.0.1072",
+        "version": "1.0.1071",
Evidence
The PR branch shows .bitmap entries with updated (lower) versions and the CLI reference docs
frontmatter indicating a different Bit version string.

.bitmap[19-39]
scopes/harmony/cli-reference/cli-reference.docs.mdx[1-4]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The PR includes large unrelated metadata changes: many version fields in `.bitmap` are decremented, and the CLI reference docs frontmatter version string changes too. This is unrelated to the export `clientId` collision fix and adds review/merge noise.
## Issue Context
`.bitmap` is workspace metadata; unexpected mass edits here are typically generated and should only be included when intentionally updating component versions.
## Fix Focus Areas
- .bitmap[19-50]
- scopes/harmony/cli-reference/cli-reference.docs.mdx[1-4]
## Suggested fix
- If these changes were accidental (e.g. generated by running a command locally): revert `.bitmap` and `cli-reference.docs.mdx` to their pre-PR state.
- If they are intentional: split them into a separate PR (or clearly explain why the version metadata must change as part of this fix) so the functional change can be reviewed/merged independently.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Qodo Logo

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 135e445

@davidfirst

Copy link
Copy Markdown
Member Author

Thanks @qodo — both findings were the same point (the now-stale "timestamp in ms" / "exact same millisecond" wording, since clientId is no longer a pure timestamp). Addressed in 135e445:

  • PushOptions.clientId doc → "opaque export identifier (timestamp-prefixed unique string), used as the pending-dir name and queue/lock key."
  • ClientIdInUse message → "another client is already exporting … using the same export id (…)", dropping the millisecond framing.

The re-review now reports Bugs (0). (Findings were posted as issue comments, so there's no inline thread to resolve.)

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Thanks @qodo — both findings were the same point (the now-stale "timestamp in ms" / "exact same millisecond" wording, since clientId is no longer a pure timestamp). Addressed in 135e445:

  • PushOptions.clientId doc → "opaque export identifier (timestamp-prefixed unique string), used as the pending-dir name and queue/lock key."
  • ClientIdInUse message → "another client is already exporting … using the same export id (…)", dropping the millisecond framing.

The re-review now reports Bugs (0). (Findings were posted as issue comments, so there's no inline thread to resolve.)

Thanks — that makes sense.

  • Updating the docs and error text to reflect the new opaque clientId behavior addresses the stale wording.
  • If the re-review is now showing Bugs 0, then there’s nothing left for me to resolve here.
  • And if the findings were only posted as issue comments, that explains why there’s no inline thread to close.

Looks good to me.

@davidfirst davidfirst enabled auto-merge (squash) June 27, 2026 00:43
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 6e7928c

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit cf4dc36

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 39be783

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 75005e9

Comment thread scopes/component/isolator/isolator.main.runtime.ts Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit a145f3b

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 6ce30a3

@davidfirst davidfirst disabled auto-merge June 30, 2026 14:04
Copilot AI review requested due to automatic review settings July 1, 2026 14:00
@davidfirst davidfirst enabled auto-merge (squash) July 1, 2026 14:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses a concurrency bug in the export flow by making the export clientId collision-resistant when multiple processes export to the same remote within the same millisecond, preventing pending-dir/queue key collisions that could lead to lost updates. It also updates related messaging/docs and includes a small TypeScript-typing workaround in isolator capsule builds.

Changes:

  • Generate export clientId as timestamp-randomSuffix instead of Date.now() only, avoiding same-ms collisions.
  • Update the “clientId in use” fatal error message and clarify clientId semantics in PushOptions.
  • Add a TS type-check suppression at the PackageJsonTransformer.applyTransformers() call site to bridge duplicate-resolution nominal type seams in dogfooding builds.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
scopes/scope/export/export.main.runtime.ts Make export clientId collision-resistant by appending a random suffix (timestamp-prefixed for queue ordering).
scopes/component/isolator/isolator.main.runtime.ts Add a TS suppression for a dogfooding-only nominal type mismatch around PackageJsonFile.
components/legacy/scope/exceptions/client-id-in-use.ts Update error message to match the new “opaque export id” meaning of clientId.
components/legacy/scope-api/lib/put.ts Update clientId inline documentation to reflect its role as an opaque identifier/lock key.

Comment thread scopes/scope/export/export.main.runtime.ts Outdated
Comment thread components/legacy/scope-api/lib/put.ts Outdated
Comment thread scopes/component/isolator/isolator.main.runtime.ts Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 5b96951

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jul 1, 2026

Copy link
Copy Markdown

Code Review by Qodo

Grey Divider

New Review Started

This review has been superseded by a new analysis

Grey Divider

Qodo Logo

@davidfirst davidfirst force-pushed the fix-concurrent-export-clientid-collision branch from 6f19858 to ede7fa3 Compare July 1, 2026 17:25
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit ede7fa3

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit a3154d7

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jul 1, 2026

Copy link
Copy Markdown

Code Review by Qodo

Grey Divider

New Review Started

This review has been superseded by a new analysis

Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 63dae48

Comment thread scopes/scope/scope/routes/put.route.ts Outdated
Comment thread .bitmap Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit f5e7f2c

Comment thread scopes/component/isolator/isolator.main.runtime.ts Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 8f90591

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 6a2156b

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit da6ba15

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 5ab2ea4

…t lost-update

Generate clientId as `-` instead of a bare
Date.now(). The clientId is the pending-dir name and the cross-client export
lock (export-validate's waitIfNeeded queue); a bare millisecond timestamp
collides when two runners export to the same remote in the same ms, sharing
one pending-dir and silently dropping one runner's update. Uses node's crypto
so this core aspect gains no new component dependency.
@davidfirst davidfirst force-pushed the fix-concurrent-export-clientid-collision branch from 5ab2ea4 to 6993ed1 Compare July 2, 2026 15:52
// sorted queue still roughly preserves arrival order) while making a same-millisecond collision
// vanishingly unlikely (64 bits of randomness). Use node's built-in `crypto` rather than a
// component helper so this core aspect doesn't gain a new component dependency.
const clientId = resumeExportId || `${Date.now()}-${crypto.randomBytes(8).toString('hex')}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Informational

1. Stale clientid messaging 🐞 Bug ⚙ Maintainability

pushToRemotesCarefully() now generates clientId as ${Date.now()}-<randomHex>, but other code
still documents/errors as if clientId were a pure millisecond timestamp. This mismatch can mislead
future callers/maintainers into adding numeric parsing/validation or misunderstanding collision
scenarios when debugging export failures.
Agent Prompt
## Issue description
`clientId` is no longer a pure millisecond timestamp (it now includes a random suffix), but some comments/error text still describe it as "timestamp in ms" / "within the exact same millisecond".

## Issue Context
The PR intentionally changes the `clientId` format to prevent same-millisecond collisions; keeping surrounding documentation accurate helps prevent future code from making incorrect assumptions about `clientId` being numeric.

## Fix Focus Areas
- components/legacy/scope-api/lib/put.ts[11-14]
- components/legacy/scope/exceptions/client-id-in-use.ts[3-10]
- (optional) scopes/scope/export/export.main.runtime.ts[682-691] (ensure comment remains accurate/consistent)

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 6993ed1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants