Skip to content

fix(cli): freeze sourcemap process selection across inject and upload - #91191

Draft
cat-ph wants to merge 2 commits into
masterfrom
cat/cli-sourcemap-process-snapshot
Draft

fix(cli): freeze sourcemap process selection across inject and upload#91191
cat-ph wants to merge 2 commits into
masterfrom
cat/cli-sourcemap-process-snapshot

Conversation

@cat-ph

@cat-ph cat-ph commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Problem

posthog-cli sourcemap process can abort a user's production build with Chunk ID not found (exit 1) when something writes into the scanned directory while the command runs. Next.js 16.3+ users hit this on every build: Turbopack's filesystem cache (on by default there) keeps flushing files into .next in the background while @posthog/nextjs-config runs the CLI, reported in posthog-js#4667.

The cause is a time-of-check/time-of-use race inside process: the inject pass and the upload pass each re-walk the directory roots independently. A chunk pair that materializes between the walks reaches upload without an injected chunk ID, and one such pair fails the entire upload.

Changes

  • sourcemap process now expands the file selection into a concrete file list once (stdin resolved, include/exclude globs applied) and hands the identical list to inject and upload, so a build no longer fails when files appear mid-run. Late files are consistently outside the run instead of half-processed.
  • The Chunk ID not found error now names the offending file.
  • The injecting selection log line is bounded by entry count and byte budget instead of printing every selected path. A materialized selection used to render as one ~1 MB line, which killed the CLI with EAGAIN when stderr was a non-blocking pipe (e.g. spawned from Node.js with inherited stdio), failing the build.
  • Mechanical: ProcessArgs::resolve_stdin became ProcessArgs::materialize; standalone sourcemap inject / sourcemap upload keep their existing selection behavior.

Companion fix on the plugin side: posthog-js#4681 makes @posthog/nextjs-config pass an explicit file snapshot, which fixes the issue for already-shipped CLI versions.

How did you test this code?

  • New unit test materialize_freezes_the_file_set: materializes a selection, writes a new file into the directory, and asserts the selection still resolves to the original set — this reproduces the regression class (it fails against the old per-pass walk).
  • New unit test materialize_fails_on_empty_selection covers the empty-directory error path.
  • New unit tests display_is_bounded_for_large_selections / display_is_bounded_for_long_paths pin the log-line bound for 10k paths and for long path names.
  • New unit test process_materialization_keeps_every_file pins that the frozen selection keeps every file: a sourceMappingURL can point at any filename, and upload's --delete-after cleanup guard derives its allowed roots from the parents of the selected files, so filtering to "candidates" would silently shrink the guard's coverage.
  • Full cargo test for the CLI crate, cargo clippy --all-targets, and cargo fmt are green locally.
  • Not checked: an end-to-end next build against a live PostHog instance with this CLI build.

Automatic notifications

  • Publish to changelog?

Docs update

Sampo changeset included (cli/.sampo/changesets/sourcemap-process-frozen-selection.md); no docs describe the two-pass internals.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

  • Claude Code (Fable 5) verified the race against Next.js 16.3.3 source: runAfterProductionCompile runs right after compile while the Turbopack shutdown/cache-flush promise is awaited only at the end of the build, so background writes overlap the hook; unchanged on canary.
  • Considered warn-and-skip on unstamped pairs instead — rejected because it can green a build whose deployed chunks silently lack sourcemaps; freezing the selection keeps the outcome deterministic.
  • Structured second-model review (Codex) over the diff reported no actionable findings.

@cat-ph cat-ph self-assigned this Aug 28, 2026
@trunk-io

trunk-io Bot commented Aug 28, 2026

Copy link
Copy Markdown

Merging to master in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

@github-actions

Copy link
Copy Markdown
Contributor

🤖 CI report

Trunk lane — non-backend lane

This PR is assigned to the non-backend lane. It does not run backend Python tests and may merge in parallel with PRs in other lanes.

@cat-ph
cat-ph marked this pull request as ready for review August 30, 2026 23:39
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 30, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-30T23:44:31.171053Z b19c12f Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@cat-ph
cat-ph requested review from a team, ablaszkiewicz and hpouillot August 30, 2026 23:39
@greptile-apps

greptile-apps Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor
Prompt To Fix All With AI
### Issue 1
cli/src/sourcemaps/args.rs:139-144
**Path snapshot leaves content race**

If a selected chunk is overwritten or its sourcemap is written after injection but before upload, the two passes read different source-pair states at the same frozen path, causing `process` to abort with `Chunk ID not found`.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "fix(cli): bound the injecting-selection ..." | Re-trigger Greptile

Comment on lines +139 to +144
let files = FileSelection::try_from(resolved.clone())?
.into_iter()
.filter(|entry| entry.file_type().is_file())
.map(|entry| entry.into_path())
.collect::<Vec<_>>();
if files.is_empty() {

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.

P1 Path snapshot leaves content race

If a selected chunk is overwritten or its sourcemap is written after injection but before upload, the two passes read different source-pair states at the same frozen path, causing process to abort with Chunk ID not found.

Prompt To Fix With AI
This is a comment left during a code review.
Path: cli/src/sourcemaps/args.rs
Line: 139-144

Comment:
**Path snapshot leaves content race**

If a selected chunk is overwritten or its sourcemap is written after injection but before upload, the two passes read different source-pair states at the same frozen path, causing `process` to abort with `Chunk ID not found`.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b19c12f32a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

bail!("No files found in {resolved}");
}
Ok(Self {
directory: files,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Stop requiring every snapshotted file to survive

When any file disappears after materialize() returns—particularly while release resolution performs network work—the materialized vector is passed into injection, whose validation now requires every path to still exist and aborts the command otherwise. Because the snapshot includes every regular file, an unrelated temporary or cache file removed by a concurrently running bundler can now fail sourcemap process; previously only the user-provided root was validated and WalkDir silently skipped vanished children. Preserve the original roots for validation or do not treat snapshot members as required inputs.

Useful? React with 👍 / 👎.

.ok_or_else(|| anyhow!("Chunk ID not found"))?;
let chunk_id = self.get_chunk_id().ok_or_else(|| {
anyhow!(
"Chunk ID not found in {} — the file was not injected before upload",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Add a recovery action to the chunk-ID error

When standalone upload encounters an uninjected source-map pair, this new error states the cause but does not tell the user how to recover, and it uses the explicitly disallowed em dash. Use direct sentences that instruct the user to run sourcemap inject first or use sourcemap process.

AGENTS.md reference: AGENTS.md:L239-L242

Useful? React with 👍 / 👎.

Comment on lines +147 to +151
Ok(Self {
directory: files,
stdin: false,
include: Vec::new(),
exclude: Vec::new(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve the original cleanup roots

With --delete-after and include/exclude filters that omit map files, replacing a directory root with only matched files narrows CSS cleanup authorization. For example, a selected dist/css/app.css referencing dist/maps/app.css.map was previously authorized by root dist, but after materialization the only relevant root is dist/css; upload succeeds while the cleanup safety check rejects the map and leaves both it and the CSS reference behind. Retain the original roots separately for cleanup validation.

Useful? React with 👍 / 👎.

@cat-ph

cat-ph commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

I think #91427 is a better approach actually

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.

1 participant