Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions cli/.sampo/changesets/release-mode-event-default.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
cargo/posthog-cli: minor
---

Default `--release-mode` to `event` for `sourcemap inject`, `sourcemap process`, `sourcemap upload`, `hermes inject`, `hermes clone`, `hermes upload` and `proguard upload`. Uploaded symbol sets, source maps and mappings are now release-independent, and each exception resolves its own release: a web build reads the `_posthogReleaseId` injected into the chunk, and a mobile build resolves it from the `$app_namespace` / `$app_version` / `$app_build` the SDK already sends. Two releases that ship the same code keep one symbol set instead of colliding on the release that uploaded it first. Pass `--release-mode symbol-set` to keep binding the release to what you upload. Before upgrading, check that the release coordinates you pass match the app's bundle identifier or applicationId, version and build number, because a mismatch leaves exceptions with no release.

`hermes upload` now resolves `--info-plist` before it checks the release coordinates. An iOS build that supplies them that way no longer gets a warning about a release the run creates correctly. `proguard upload` now warns about a missing `--build`, matching `hermes upload`.
28 changes: 14 additions & 14 deletions cli/src/proguard/upload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,17 @@ pub struct Args {
#[clap(flatten)]
pub conflict: UploadConflictArgs,

/// How the release is associated with exceptions. `symbol-set`, the default, stamps the
/// release id onto the uploaded mapping, and an exception takes the release of the mappings
/// its frames resolved against. EXPERIMENTAL `event` leaves the mapping
/// How the release is associated with exceptions. `event`, the default, leaves the mapping
/// release-independent, and each event resolves its own release from the app version and
/// namespace the SDK already sends, so the release coordinates have to match the app's. The
/// release is created either way. Also settable via `POSTHOG_RELEASE_MODE`.
/// namespace the SDK already sends, so the release coordinates have to match the app's.
/// `symbol-set` stamps the release id onto the uploaded mapping instead, and an exception
/// then takes the release of the mappings its frames resolved against. The release is created
/// either way. Also settable via `POSTHOG_RELEASE_MODE`.
#[arg(
long,
env = "POSTHOG_RELEASE_MODE",
value_enum,
default_value = "symbol-set"
default_value = "event"
Comment thread
ablaszkiewicz marked this conversation as resolved.
Comment thread
ablaszkiewicz marked this conversation as resolved.
)]
pub release_mode: ReleaseMode,
}
Expand Down Expand Up @@ -69,7 +69,7 @@ pub fn upload(args: &Args) -> Result<()> {
// exception resolves its release only from the app metadata on the event itself. Coordinates
// derived from git rather than passed explicitly will not match that metadata, and the
// exception then reports no release at all, silently.
if *release_mode == ReleaseMode::Event && (name.is_none() || version.is_none()) {
if *release_mode == ReleaseMode::Event && !resolved_release.event_coordinates_complete() {
warn!(
"--release-mode=event resolves each exception's release from the app's namespace and \
version. Pass --release-name, --release-version and --build matching the app's \
Expand Down Expand Up @@ -159,17 +159,17 @@ mod tests {
}

#[test]
fn defaults_to_binding_the_release_to_the_mapping() {
// Every existing caller omits the flag, and they must keep uploading mappings stamped with
// their release.
assert_eq!(parse(&[]).release_mode, ReleaseMode::SymbolSet);
fn defaults_to_event_release_mode() {
// A caller that omits the flag uploads the mapping release-independent, so several
// releases that share one mapping keep one symbol set.
assert_eq!(parse(&[]).release_mode, ReleaseMode::Event);
}

#[test]
fn accepts_event_release_mode() {
fn accepts_symbol_set_release_mode() {
assert_eq!(
parse(&["--release-mode", "event"]).release_mode,
ReleaseMode::Event
parse(&["--release-mode", "symbol-set"]).release_mode,
ReleaseMode::SymbolSet
);
}
}
43 changes: 38 additions & 5 deletions cli/src/sourcemaps/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@ impl FileSelectionArgs {
/// How exceptions get associated with a release.
#[derive(clap::ValueEnum, Clone, Copy, Debug, PartialEq, Eq)]
pub enum ReleaseMode {
/// Bind the release to the uploaded symbol sets (the previous behavior)
/// Bind the release to the uploaded symbol sets
SymbolSet,
/// EXPERIMENTAL: resolve the release per event from an id injected into each chunk
/// Resolve the release per event from an id injected into each chunk (the default)
Event,
}

Expand Down Expand Up @@ -135,13 +135,14 @@ pub struct ReleaseArgs {

#[derive(clap::Args, Clone, Default)]
pub struct UploadConflictArgs {
/// Allow overwriting an existing symbol set whose content has changed. Always on with
/// `--release-mode=event`. [default: false]
/// Allow overwriting an existing symbol set whose content has changed. A sourcemap upload
/// always overwrites with `--release-mode=event`. A proguard upload does not. [default: false]
#[arg(long, default_value_t = false, conflicts_with = "skip_on_conflict")]
pub force: bool,

/// Skip symbol sets that already exist with different content instead of failing.
/// Existing symbol sets are left unchanged. Ignored with `--release-mode=event`. [default: false]
/// Existing symbol sets are left unchanged. A sourcemap upload ignores this with
/// `--release-mode=event`. A proguard upload honors it. [default: false]
#[arg(long, default_value_t = false, conflicts_with = "force")]
pub skip_on_conflict: bool,
}
Expand Down Expand Up @@ -189,6 +190,16 @@ impl ReleaseArgs {
self.resolve_info_plist_with_environment(|name| std::env::var(name).ok())
}

/// Whether the coordinates identify a release an event can resolve.
///
/// An event carries its own app metadata, and the server keys a release on the name and the
/// packed version. It packs the build into that version, so a release without a build matches
/// no event that sends `$app_build`. Call this on resolved args, because `--info-plist` fills
/// the same three fields.
pub fn event_coordinates_complete(&self) -> bool {
self.name.is_some() && self.version.is_some() && self.build.is_some()
}

fn resolve_info_plist_with_environment<F>(&self, environment: F) -> Result<Self>
where
F: Fn(&str) -> Option<String>,
Expand Down Expand Up @@ -272,6 +283,28 @@ mod tests {
}
}

#[test]
fn event_mode_needs_every_release_coordinate() {
// The server packs the build into the version it keys a release on, so a release without
// one matches no event that sends `$app_build`.
let cases = [
// name, version, build, complete
(Some("com.app"), Some("1.0"), Some("42"), true),
(Some("com.app"), Some("1.0"), None, false),
(Some("com.app"), None, Some("42"), false),
(None, Some("1.0"), Some("42"), false),
(None, None, None, false),
];

for (name, version, build, complete) in cases {
assert_eq!(
make_args(name, version, build).event_coordinates_complete(),
complete,
"name={name:?} version={version:?} build={build:?}"
);
}
}

#[test]
fn release_args_to_builder() {
let cases: Vec<(Option<&str>, Option<&str>, bool)> = vec![
Expand Down
12 changes: 6 additions & 6 deletions cli/src/sourcemaps/hermes/clone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,17 @@ pub struct CloneArgs {
#[clap(flatten)]
pub release: ReleaseArgs,

/// How the release is associated with exceptions. `symbol-set` is the default. It stamps the
/// release id into the source maps, so the upload binds the symbol set to that release.
/// EXPERIMENTAL `event` stamps nothing and leaves the maps release-independent. Each event
/// then resolves its own release from the app version and namespace the SDK already sends.
/// The coordinates you pass to `hermes upload` must match the app's. Also settable via
/// How the release is associated with exceptions. `event` is the default. It stamps nothing
/// and leaves the maps release-independent. Each event then resolves its own release from the
/// app version and namespace the SDK already sends. The coordinates you pass to
/// `hermes upload` must match the app's. `symbol-set` stamps the release id into the source
/// maps instead, so the upload binds the symbol set to that release. Also settable via
/// `POSTHOG_RELEASE_MODE`.
#[arg(
long,
env = "POSTHOG_RELEASE_MODE",
value_enum,
default_value = "symbol-set"
default_value = "event"
)]
pub release_mode: ReleaseMode,
}
Expand Down
41 changes: 30 additions & 11 deletions cli/src/sourcemaps/hermes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,10 @@ mod tests {
}

#[test]
fn every_hermes_command_defaults_to_binding_the_release() {
// Every existing React Native build omits the flag. Those builds must keep uploading
// maps bound to their release. A different default here unbinds all of them, and
// nothing in the build output says so.
fn every_hermes_command_defaults_to_event_release_mode() {
// A build that omits the flag must leave its maps release-independent, so two releases
// shipping the same JavaScript keep one symbol set instead of colliding on whichever
// release uploaded it first.
let HermesSubcommand::Clone(clone) = parse(&[
"hermes",
"clone",
Expand All @@ -86,17 +86,24 @@ mod tests {
]) else {
panic!("expected the clone subcommand");
};
assert_eq!(clone.release_mode, ReleaseMode::SymbolSet);
assert_eq!(clone.release_mode, ReleaseMode::Event);

let HermesSubcommand::Upload(upload) = parse(&["hermes", "upload", "--directory", "dist"])
else {
panic!("expected the upload subcommand");
};
assert_eq!(upload.release_mode, ReleaseMode::SymbolSet);
assert_eq!(upload.release_mode, ReleaseMode::Event);

// `hermes inject` shares InjectArgs with `sourcemap inject`, so it takes the same default.
let HermesSubcommand::Inject(inject) = parse(&["hermes", "inject", "--directory", "dist"])
else {
panic!("expected the inject subcommand");
};
assert_eq!(inject.release_mode, ReleaseMode::Event);
}

#[test]
fn every_hermes_command_accepts_event_release_mode() {
fn every_hermes_command_accepts_symbol_set_release_mode() {
let HermesSubcommand::Clone(clone) = parse(&[
"hermes",
"clone",
Expand All @@ -105,22 +112,34 @@ mod tests {
"--composed-map-path",
"main.jsbundle.hbc.composed.map",
"--release-mode",
"event",
"symbol-set",
]) else {
panic!("expected the clone subcommand");
};
assert_eq!(clone.release_mode, ReleaseMode::Event);
assert_eq!(clone.release_mode, ReleaseMode::SymbolSet);

let HermesSubcommand::Upload(upload) = parse(&[
"hermes",
"upload",
"--directory",
"dist",
"--release-mode",
"event",
"symbol-set",
]) else {
panic!("expected the upload subcommand");
};
assert_eq!(upload.release_mode, ReleaseMode::Event);
assert_eq!(upload.release_mode, ReleaseMode::SymbolSet);

let HermesSubcommand::Inject(inject) = parse(&[
"hermes",
"inject",
"--directory",
"dist",
"--release-mode",
"symbol-set",
]) else {
panic!("expected the inject subcommand");
};
assert_eq!(inject.release_mode, ReleaseMode::SymbolSet);
}
}
25 changes: 13 additions & 12 deletions cli/src/sourcemaps/hermes/upload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,17 @@ pub struct Args {
#[clap(flatten)]
pub conflict: UploadConflictArgs,

/// How the release is associated with exceptions. `symbol-set` is the default. It stamps the
/// release id onto the uploaded maps. An exception then takes the release of the maps its
/// frames resolved against. EXPERIMENTAL `event` leaves the maps release-independent. Each
/// event then resolves its own release from the app version and namespace the SDK already
/// sends, so the release coordinates must match the app's. Both modes create the release.
/// How the release is associated with exceptions. `event` is the default. It leaves the maps
/// release-independent. Each event then resolves its own release from the app version and
/// namespace the SDK already sends, so the release coordinates must match the app's.
/// `symbol-set` stamps the release id onto the uploaded maps instead, and an exception then
/// takes the release of the maps its frames resolved against. Both modes create the release.
/// Also settable via `POSTHOG_RELEASE_MODE`.
#[arg(
long,
env = "POSTHOG_RELEASE_MODE",
value_enum,
default_value = "symbol-set"
default_value = "event"
Comment thread
ablaszkiewicz marked this conversation as resolved.
)]
pub release_mode: ReleaseMode,
}
Expand All @@ -60,15 +60,16 @@ pub fn upload(args: &Args) -> Result<()> {
);
}

// `--info-plist` fills the same fields the check below reads, and it fills them here rather
// than inside `get_release_for_maps`. Checking the raw args warns about a missing release that
// the run goes on to create.
let release = release.resolve_info_plist()?;

// Event mode leaves nothing on the symbol set for the server to use. An exception then
// resolves its release only from the app metadata on the event. Coordinates that come from
// git instead of explicit flags do not match that metadata. The exception then reports no
// release, and nothing in the output says so. The build number counts as a coordinate: the
// server packs it into the version it keys on, so a release without one matches no event that
// carries `$app_build`.
if *release_mode == ReleaseMode::Event
&& (release.name.is_none() || release.version.is_none() || release.build.is_none())
{
// release, and nothing in the output says so.
if *release_mode == ReleaseMode::Event && !release.event_coordinates_complete() {
warn!(
"--release-mode=event resolves each exception's release from the app's namespace and \
version. Pass --release-name, --release-version and --build matching the app's bundle \
Expand Down
15 changes: 8 additions & 7 deletions cli/src/sourcemaps/inject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,18 @@ pub struct InjectArgs {
#[clap(flatten)]
pub release: ReleaseArgs,

/// How the release is associated with exceptions. `symbol-set` (the default) stamps the
/// release id into the sourcemap so the uploaded symbol set is bound to it: the previous
/// behavior. EXPERIMENTAL `event` injects the release id into each chunk as
/// `_posthogReleaseId` so the SDK emits it on every exception, and derives
/// content-addressed chunk ids that are stable across rebuilds. Also settable via
/// `POSTHOG_RELEASE_MODE`.
/// How the release is associated with exceptions. `event` (the default) derives
/// content-addressed chunk ids that are stable across rebuilds. A web build also injects the
/// release id into each chunk as `_posthogReleaseId`, so the SDK emits it on every exception.
/// A Hermes build injects no release id, because a bytecode bundle cannot read one back out.
/// Each of its events resolves the release from the app version and namespace the SDK sends.
/// `symbol-set` stamps the release id into the sourcemap instead, so the uploaded symbol set
/// is bound to it. Also settable via `POSTHOG_RELEASE_MODE`.
#[arg(
long,
env = "POSTHOG_RELEASE_MODE",
value_enum,
default_value = "symbol-set"
default_value = "event"
Comment thread
ablaszkiewicz marked this conversation as resolved.
)]
pub release_mode: ReleaseMode,
}
Expand Down
12 changes: 6 additions & 6 deletions cli/src/sourcemaps/plain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,16 @@ pub struct ProcessArgs {
#[clap(flatten)]
pub upload_concurrency: UploadConcurrencyArgs,

/// How the release is associated with exceptions. `symbol-set` (the default) stamps the
/// release id onto the uploaded symbol sets: the previous behavior. EXPERIMENTAL `event`
/// injects the release id into each chunk as `_posthogReleaseId` (alongside
/// content-addressed chunk ids) so the SDK reports the release per event; symbol sets stay
/// release-independent. Also settable via `POSTHOG_RELEASE_MODE`.
/// How the release is associated with exceptions. `event` (the default) injects the release
/// id into each chunk as `_posthogReleaseId` (alongside content-addressed chunk ids) so the
/// SDK reports the release per event; symbol sets stay release-independent. `symbol-set`
/// stamps the release id onto the uploaded symbol sets instead. Also settable via
/// `POSTHOG_RELEASE_MODE`.
#[arg(
long,
env = "POSTHOG_RELEASE_MODE",
value_enum,
default_value = "symbol-set"
default_value = "event"
)]
pub release_mode: ReleaseMode,
}
Expand Down
11 changes: 5 additions & 6 deletions cli/src/sourcemaps/plain/upload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,15 @@ pub struct Args {
#[arg(long)]
pub skip_ssl_verification: bool,

/// How the release is associated with exceptions. `symbol-set` (the default) stamps the
/// release id onto the uploaded symbol sets: the previous behavior. EXPERIMENTAL `event`
/// leaves symbol sets unbound; the chunks already carry the release id in their injected
/// snippet, so the release is resolved per event rather than per symbol set. Also settable
/// via `POSTHOG_RELEASE_MODE`.
/// How the release is associated with exceptions. `event` (the default) leaves symbol sets
/// unbound; the chunks already carry the release id in their injected snippet, so the release
/// is resolved per event rather than per symbol set. `symbol-set` stamps the release id onto
/// the uploaded symbol sets instead. Also settable via `POSTHOG_RELEASE_MODE`.
#[arg(
long,
env = "POSTHOG_RELEASE_MODE",
value_enum,
default_value = "symbol-set"
default_value = "event"
Comment thread
ablaszkiewicz marked this conversation as resolved.
Comment on lines +69 to +77

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.

Event uploads can keep source maps with wrong offsets

must_fix bug

Why we think it's a valid issue
  • Checked: into_upload in cli/src/sourcemaps/source_pairs.rs, the snippet templates in cli/src/sourcemaps/constant.rs, set_chunk_id / remove_chunk_id in cli/src/sourcemaps/content.rs, the server's skip-or-overwrite branch in products/error_tracking/backend/logic/symbol_sets.py, and the event-mode hash tests in cli/tests/sourcemap.rs.
  • Found: the payload and the hash are taken from different states of the pair. cli/src/sourcemaps/source_pairs.rs:165-166 captures source_content and sourcemap_content for data while the injection is still present, and :170 strips it only afterwards to compute the hash. So data carries the snippet variant and content_hash does not.
  • Found: the two variants really do shift generated columns. Both templates in cli/src/sourcemaps/constant.rs:2 and :8 are single-line and end in }(); with no newline, and cli/src/sourcemaps/content.rs:269-272 prepends the snippet directly before the first character. The release variant replaces , with ;e._posthogReleaseId=e._posthogReleaseId||"<36-char uuid>";var , roughly 80 characters, so every line-1 mapping moves by that amount. Minified bundles put most code on line 1.
  • Found: the equal hash is deliberate and test-pinned. test_event_mode_content_hash_is_stable_across_release_states at cli/tests/sourcemap.rs:474-509 asserts releaseless == with_release, and the comment at cli/src/sourcemaps/source_pairs.rs:150-156 names the exact case β€” a chunk injected before a release resolved "carries a shorter snippet (and a differently adjusted sourcemap)". The design knowingly hashes past a map difference to avoid a false content_hash_mismatch.
  • Found: force does not rescue it. products/error_tracking/backend/logic/symbol_sets.py:246-249 takes the equal-hash branch and only passes, issuing no presigned URL, so the newer payload is never sent. force is read at :250, which is reachable only when the hashes differ. Event mode always sets force: true, and it has no effect here.
  • Impact: after a chunk transitions between the no-release and with-release states without its pristine content changing, the stored map keeps the old snippet's offsets while the deployed chunk has the new one. Frames then resolve about 80 generated columns away, which in minified code can be a different function. The wrong map persists until the pristine content changes, and nothing reports it.
  • Impact (scope): a release-to-release change is not affected, because a UUID is always 36 characters, so the snippet length and the mappings stay identical β€” that case is covered by test_event_mode_content_hash_is_stable_across_releases_for_adopted_ids at cli/tests/sourcemap.rs:620. The harm needs the length to change, so it hits projects whose earlier uploads had no resolvable release (cli/src/sourcemaps/inject.rs:98-102 warns in that state) and that later gain one. Unchanged vendor chunks are the most exposed, since their pristine content survives many deploys. This PR makes event mode the default at cli/src/sourcemaps/plain/upload.rs:77, so the path now runs for every web upload.
Issue description

Event mode removes the injected snippet before it calculates content_hash. A chunk without a release and the same chunk with a release therefore have the same hash. Their uploaded source maps differ because the release snippet is longer and shifts generated columns. The server skips the second payload as identical. It then uses old column offsets for new exceptions, which can resolve frames to wrong source positions.

Suggested fix

Include the snippet layout in the event-mode hash. For example, append a marker when _posthogReleaseId is present. This makes the no-release-to-release transition overwrite the stored map. Keep the hash stable when only the release UUID changes. Add a test for both transitions.

Prompt to fix with AI (copy-paste)
## Context
@cli/src/sourcemaps/plain/upload.rs#L69-77

<issue_description>
Event mode removes the injected snippet before it calculates `content_hash`. A chunk without a release and the same chunk with a release therefore have the same hash. Their uploaded source maps differ because the release snippet is longer and shifts generated columns. The server skips the second payload as identical. It then uses old column offsets for new exceptions, which can resolve frames to wrong source positions.
</issue_description>

<issue_validation>
- **Checked:** `into_upload` in `cli/src/sourcemaps/source_pairs.rs`, the snippet templates in `cli/src/sourcemaps/constant.rs`, `set_chunk_id` / `remove_chunk_id` in `cli/src/sourcemaps/content.rs`, the server's skip-or-overwrite branch in `products/error_tracking/backend/logic/symbol_sets.py`, and the event-mode hash tests in `cli/tests/sourcemap.rs`.
- **Found:** the payload and the hash are taken from different states of the pair. `cli/src/sourcemaps/source_pairs.rs:165-166` captures `source_content` and `sourcemap_content` for `data` while the injection is still present, and `:170` strips it only afterwards to compute the hash. So `data` carries the snippet variant and `content_hash` does not.
- **Found:** the two variants really do shift generated columns. Both templates in `cli/src/sourcemaps/constant.rs:2` and `:8` are single-line and end in `}();` with no newline, and `cli/src/sourcemaps/content.rs:269-272` prepends the snippet directly before the first character. The release variant replaces `,` with `;e._posthogReleaseId=e._posthogReleaseId||"<36-char uuid>";var `, roughly 80 characters, so every line-1 mapping moves by that amount. Minified bundles put most code on line 1.
- **Found:** the equal hash is deliberate and test-pinned. `test_event_mode_content_hash_is_stable_across_release_states` at `cli/tests/sourcemap.rs:474-509` asserts `releaseless == with_release`, and the comment at `cli/src/sourcemaps/source_pairs.rs:150-156` names the exact case β€” a chunk injected before a release resolved "carries a shorter snippet (and a differently adjusted sourcemap)". The design knowingly hashes past a map difference to avoid a false `content_hash_mismatch`.
- **Found:** `force` does not rescue it. `products/error_tracking/backend/logic/symbol_sets.py:246-249` takes the equal-hash branch and only `pass`es, issuing no presigned URL, so the newer payload is never sent. `force` is read at `:250`, which is reachable only when the hashes differ. Event mode always sets `force: true`, and it has no effect here.
- **Impact:** after a chunk transitions between the no-release and with-release states without its pristine content changing, the stored map keeps the old snippet's offsets while the deployed chunk has the new one. Frames then resolve about 80 generated columns away, which in minified code can be a different function. The wrong map persists until the pristine content changes, and nothing reports it.
- **Impact (scope):** a release-to-release change is not affected, because a UUID is always 36 characters, so the snippet length and the mappings stay identical β€” that case is covered by `test_event_mode_content_hash_is_stable_across_releases_for_adopted_ids` at `cli/tests/sourcemap.rs:620`. The harm needs the length to change, so it hits projects whose earlier uploads had no resolvable release (`cli/src/sourcemaps/inject.rs:98-102` warns in that state) and that later gain one. Unchanged vendor chunks are the most exposed, since their pristine content survives many deploys. This PR makes event mode the default at `cli/src/sourcemaps/plain/upload.rs:77`, so the path now runs for every web upload.
</issue_validation>

## Task
Investigate the issue and solve it

<potential_solution>
Include the snippet layout in the event-mode hash. For example, append a marker when `_posthogReleaseId` is present. This makes the no-release-to-release transition overwrite the stored map. Keep the hash stable when only the release UUID changes. Add a test for both transitions.
</potential_solution>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in #92250, which lands before this PR. The hash there covers which snippet the chunk carries, and still ignores the release id, so a new release does not re-upload every chunk.

One correction from testing that PR. The collision is measurable on real esbuild output at the library level: the old hash is identical for both snippet variants. It does not reproduce end to end through sourcemap process, where the restored map already differs between the variants and separates the two hashes. #92250 makes the property hold by construction rather than by that difference. Both transcripts are in its description.

)]
pub release_mode: ReleaseMode,
}
Expand Down
Loading