fix(export): make export clientId collision-safe to prevent concurrent lost-update#10458
fix(export): make export clientId collision-safe to prevent concurrent lost-update#10458davidfirst wants to merge 1 commit into
Conversation
PR Summary by QodoFix export clientId collisions by adding random suffix for concurrent runs Description
Diagram
High-Level Assessment
Files changed (1)
|
Code Review by Qodo
1.
|
|
Code review by qodo was updated up to the latest commit 135e445 |
|
Thanks @qodo — both findings were the same point (the now-stale "timestamp in ms" / "exact same millisecond" wording, since
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.
Looks good to me. |
|
Code review by qodo was updated up to the latest commit 6e7928c |
|
Code review by qodo was updated up to the latest commit cf4dc36 |
|
Code review by qodo was updated up to the latest commit 39be783 |
|
Code review by qodo was updated up to the latest commit 75005e9 |
|
Code review by qodo was updated up to the latest commit a145f3b |
|
Code review by qodo was updated up to the latest commit 6ce30a3 |
There was a problem hiding this comment.
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
clientIdastimestamp-randomSuffixinstead ofDate.now()only, avoiding same-ms collisions. - Update the “clientId in use” fatal error message and clarify
clientIdsemantics inPushOptions. - 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. |
|
Code review by qodo was updated up to the latest commit 5b96951 |
6f19858 to
ede7fa3
Compare
|
Code review by qodo was updated up to the latest commit ede7fa3 |
|
Code review by qodo was updated up to the latest commit a3154d7 |
|
Code review by qodo was updated up to the latest commit 63dae48 |
|
Code review by qodo was updated up to the latest commit f5e7f2c |
|
Code review by qodo was updated up to the latest commit 8f90591 |
|
Code review by qodo was updated up to the latest commit 6a2156b |
|
Code review by qodo was updated up to the latest commit da6ba15 |
|
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.
5ab2ea4 to
6993ed1
Compare
| // 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')}`; |
There was a problem hiding this comment.
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
|
Code review by qodo was updated up to the latest commit 6993ed1 |
Problem
export'sclientIdis both the pending-dir name and the cross-client export lock —export-validate'swaitIfNeededqueue sorts pending-dir names and lets only the first proceed to validate+persist. It was generated asDate.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.
--resumeis unaffected (it echoes back a user-supplied id; clientId is never parsed numerically).