Skip to content

fix(cli): preserve project assets when archive paths collide - #6722

Open
AksharP5 wants to merge 1 commit into
heygen-com:mainfrom
AksharP5:archive-asset-collision
Open

AksharP5 wants to merge 1 commit into
heygen-com:mainfrom
AksharP5:archive-asset-collision

Conversation

@AksharP5

@AksharP5 AksharP5 commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

What

Publishing or preparing a cloud render can silently replace an existing project asset with a different sibling asset. Keep both files and give the new copy an unused name.

Why

A project unpacked from a HyperFrames archive can already contain _ext/shared/logo.svg. If it later references ../shared/logo.svg, packing replaces the existing asset's bytes while leaving its references in place. Archive preparation succeeds, but the original image becomes the wrong logo.

The synthetic reproduction has a red project logo, a blue logo_2.svg control, and a green external logo referenced twice. On the same inputs:

Observation Main 49e031230 This change
Public cloud render --dry-run --json Exit 0, file_count: 3 Exit 0, file_count: 4
ZIP _ext/shared/logo.svg Green, replaces red Original red
ZIP _ext/shared/logo_2.svg Original blue Original blue
Green asset's two rewritten references Both _ext/shared/logo.svg Both _ext/shared/logo_3.svg
original red preserved false true
new green separately archived false true
Source project files Unchanged Unchanged

Related work

Fixes #6721.

#1160 introduced localization and the suffix loop for collisions between external files. Its occupied-name set starts empty, so it overlooks staged project files. #6265/#6266 concern URL decoding and already document query/fragment omissions; those are outside this fix. #6687 changes OAuth endpoints in the same source file and does not touch localization.

Live open/closed issue and PR searches and related review discussions were refreshed before filing. No matching report or active work was found for overwriting an already staged project key.

How

Initialize the existing occupied-name set from the staged file-map keys. Reuse the existing suffix loop and external-file deduplication. No dependencies or new naming mechanism.

Reproduce

Run this from a repository checkout after bun install --frozen-lockfile and bun run build. It creates only synthetic assets and uses the real CLI dry-run without uploading a project or starting a cloud job. The ZIP inspection reads the same archive helper used by publish.

#!/usr/bin/env bash
set -euo pipefail
# Run from a built repository checkout. All inputs are synthetic.
proof_dir=$(mktemp -d)
mkdir -p "$proof_dir/project/_ext/shared" "$proof_dir/shared"
for pair in 'project/_ext/shared/logo.svg red' 'project/_ext/shared/logo_2.svg blue' 'shared/logo.svg green'; do
  read -r name color <<< "$pair"
  printf '<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"><rect width="100" height="100" fill="%s"/></svg>' "$color" > "$proof_dir/$name"
done
cat > "$proof_dir/project/index.html" <<'HTML'
<html><body><div data-composition-id="logos" data-width="400" data-height="100" data-duration="1" data-no-timeline><img src="_ext/shared/logo.svg"><img src="_ext/shared/logo_2.svg"><img src="../shared/logo.svg"><img src="../shared/logo.svg"></div></body></html>
HTML
HYPERFRAMES_NO_UPDATE_CHECK=1 HYPERFRAMES_SKIP_SKILLS=1 DO_NOT_TRACK=1 node packages/cli/bin/hyperframes.mjs cloud render "$proof_dir/project" --dry-run --json
(cd packages/cli && bun --eval '
import { createPublishArchive } from "./src/utils/publishProject.ts";
import AdmZip from "adm-zip";
const zip = new AdmZip(createPublishArchive(process.argv[1]).buffer);
console.log("entries", zip.getEntries().map(entry => entry.entryName));
console.log("original red preserved", zip.readAsText("_ext/shared/logo.svg").includes("fill=\"red\""));
console.log("new green separately archived", zip.readAsText("_ext/shared/logo_3.svg").includes("fill=\"green\""));
console.log("HTML", zip.readAsText("index.html"));
' "$proof_dir/project")
printf 'Synthetic fixture retained at %s\n' "$proof_dir"

Test plan

  • fails without the fix: packages/cli/src/utils/publishProject.collision.test.ts, both preserves existing archive paths when localizing assets cases, thresholds Infinity and 1. With publishProject.ts from main 49e031230, both fail with expected 3 to be 4; both pass at 9b849c06. They cover in-memory and disk staging, an occupied _2 suffix, deduplication, ZIP contents, rewritten references, and unchanged source files.
  • Same-input built CLI dry-run and ZIP comparison shown above.
  • Full workspace bun run build, then rebuilt CLI.
  • 93 tests across archive construction, srcset localization, proxy baking, cloud render, and the new collision cases.
  • CLI typecheck, oxlint, oxfmt, comment citations, and comment-share check.
  • Normal pre-commit and commit-message hooks, including Fallow, project process ownership, tracked artifacts, large files, and core/Studio/scripts typechecks.
  • Documentation updated.
  • Comments follow CONTRIBUTING.md. No code comments added.

Limitations: Linux arm64 verification; no authenticated publish/cloud request was sent. Mintlify validate and broken-links could not run because mint is not installed. The documentation edit is prose only and adds no links or MDX components.

@mintlify

mintlify Bot commented Oct 29, 2026

Copy link
Copy Markdown

Preview approval required

This pull request comes from a fork, so Mintlify won't build a preview until someone with write access approves commit 9b849c0.

Approve preview

@AksharP5

AksharP5 commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor Author

Independent GPT-6 Astra high review of exact head 9b849c06709226bb69b2e47ad32c8be8b260a11e found no actionable introduced findings.

The reviewer independently reran the public CLI dry-run/ZIP reproduction and 73 focused tests. Existing project assets survive, occupied suffixes are skipped, repeated external references remain deduplicated, and source files stay unchanged. The published patch, tree and actual merge-base were verified.

Review reused the verified existing build on Linux arm64; no independent full rebuild, native Windows/macOS, authenticated upload or hosted render was performed. All 12 required checks pass, including the replacement Windows run; the final snapshot has no failed or pending checks. The requested failing-test line is present in the PR description. Jrusso approved this exact head and confirmed the description meets Genesis's request. Genesis's earlier body-only change request remains active, so GitHub still reports CHANGES_REQUESTED. No merge performed.

Windows CI follow-up: the first engine-cli run failed in five cli.agentMode.test.ts help-command subprocesses that reached the unchanged 15-second limit: init, add, catalog, media-use doctor, and play. Both added collision cases passed in that same Windows job. The failing test file and CLI dispatch source are unchanged from the merge-base; the changed archive allocation runs inside project staging.

The complete agent-mode file passes locally on Linux arm64, 435 tests with the existing time limits. This does not establish the Windows timeout cause. The replacement Windows run passed on the same head, including engine-cli and the aggregate gate. No source or timeout changes were made for this failure.

Root follow-up on 2026-10-30: all 73 focused archive tests passed (two hosted e2e tests skipped). The public CLI dry-run and independent ZIP inspection retained the original red and blue project assets, placed the green external asset at logo_3.svg, and rewrote both references to that single copy. The exact-head checkout remains clean. This reused the existing verified build on Linux arm64; no authenticated upload or hosted render was run.

@heygengenesis heygengenesis Bot 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.

Verdict: REQUEST CHANGES · 1 blocker · 0 important · 0 nits · head 9b849c06

  • blocker: the body has no fails without the fix: line naming the test. This lane needs one on every fix PR. Something like fails without the fix: publishProject.collision.test.ts, both cases (expected 3 to be 4 with publishProject.ts from main). It's a body-only change; the code needs nothing.

The fix is right: seeding usedArchivePaths with the staged keys sends a colliding external copy to the next free _N suffix, so it can't overwrite a project file.

Scope

Read publishProject.ts addExternalAsset/localizeExternalAssets, the new test and the docs paragraph. Locally, both new cases pass at this head and fail with publishProject.ts from main (expected 3 to be 4). All 73 publishProject* tests pass. CI and Windows render were still running when this was posted.

@AksharP5

Copy link
Copy Markdown
Contributor Author

Added the requested fails without the fix: line to the Test plan, naming publishProject.collision.test.ts and both parameterized cases. With the main implementation, both fail with expected 3 to be 4; both pass with this fix. This matches the retained baseline log and your independent reproduction.

The change is to the PR description only; head remains 9b849c06709226bb69b2e47ad32c8be8b260a11e.

SanHsien added a commit to SanHsien/hyperframes that referenced this pull request Oct 30, 2026
…name collision, Windows contact-sheet labels)

publishProject seeds usedArchivePaths from fileContents.keys(), so external files no longer
overwrite existing _ext/ names; contactSheet uses node:path basename for Windows paths.
Both match the upstream PR diffs line for line; DECISIONS records the adoption.

Executed from the roadmap's near-term table; the finished row is removed in
the same commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: SanHsien <34234698+SanHsien@users.noreply.github.com>

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed at 9b849c06709226bb69b2e47ad32c8be8b260a11e.

I checked archive allocation and its callers. Seeding the occupied names from staged keys preserves project assets while reusing the existing suffix allocator. The body now contains the regression evidence requested by Genesis.

Attempted vitest publishProject.collision.test.ts: collection blocked by missing adm-zip. Genesis independently recorded 73 passing tests at this exact SHA. I read the implementation and tests but did not independently execute archive creation. Root adversarial check required.

Need: yes · Size: ok · Reuse: existing implementation seam · Simpler: already minimal · Structure: fine
Risk: medium - archive asset identity and collision handling.

Verdict: APPROVE
Reasoning: I checked archive allocation and its callers. Seeding the occupied names from staged keys preserves project assets while reusing the existing suffix allocator. The body now contains the regression evidence requested by Genesis.
The current description now includes the requested fails without the fix: test evidence. Genesis's earlier current-head body-only change request is still live.

Reasoning: I checked archive allocation and its callers. Seeding the occupied names from staged keys preserves project assets while reusing the existing suffix allocator. The body now contains the regression evidence requested by Genesis.

dgh52ohp125-tech added a commit to dgh52ohp125-tech/hyperframes that referenced this pull request Nov 5, 2026
Backport the collision allocator fix and regression coverage from
heygen-com#6722 by AksharP5,
upstream head 9b849c0.

Seed occupied archive paths from staged project files so external assets
use the next free suffix instead of replacing existing source assets.
Document the behavior and retain upstream licensing and provenance.

Both regression cases fail without the fix. Verified 82 archive/proxy
tests, CLI build/typecheck, lint/format/comment gates, and an independent
review. Real source and built-module ZIP checks preserve all three
synthetic logos and original source hashes, with network/process traps.
No upload, cloud render, browser playback, or native OS parity claimed.

This branch has not been deployed

No deployments
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.

Project archives silently replace existing assets when external paths collide

2 participants