Skip to content

Encoded external asset paths are omitted from project archives #6265

Closed

Description

@AksharP5

Describe the bug

A project can use ../shared/poster%20art.svg successfully locally, then silently lose the poster when it is packed for publish or cloud rendering. The archive localizer looks for a literal %20 filename rather than poster art.svg. Unicode escapes fail the same way.

Minimal reproduction

From a built repository checkout, create a synthetic project and sibling poster:

mkdir -p archive-repro/project/assets archive-repro/shared
ffmpeg -y -f lavfi -i color=c=black:s=320x180:d=1 -an -c:v libx264 -pix_fmt yuv420p archive-repro/project/assets/clip.mp4
cat > 'archive-repro/shared/poster art.svg' <<'SVG'
<svg xmlns="http://www.w3.org/2000/svg" width="320" height="180"><path fill="#00ff00" d="M0 0h320v180H0z"/></svg>
SVG
cat > archive-repro/project/index.html <<'HTML'
<!doctype html><html lang="en"><head><meta charset="UTF-8"><title>External poster</title>
<style>html,body{margin:0;width:100%;height:100%}#root{width:100%;height:100%}video{width:100%;height:100%;object-fit:cover}</style></head><body>
<div id="root" data-composition-id="poster-probe" data-no-timeline data-width="320" data-height="180" data-duration="1">
<video class="clip" data-start="0" data-duration="1" data-track-index="0" muted playsinline src="assets/clip.mp4" poster="../shared/poster%20art.svg"></video>
</div></body></html>
HTML

Use the same staging helper consumed by publish and cloud render to inspect the ZIP without uploading:

bun --eval 'import { writeFileSync } from "node:fs"; import { createPublishArchive } from "./packages/cli/src/utils/publishProject.ts"; const archive = createPublishArchive("archive-repro/project"); writeFileSync("archive-repro/project.zip", archive.buffer); console.log(archive.fileCount);'
unzip -l archive-repro/project.zip

Steps to reproduce

  1. Open the original project. The green poster loads.
  2. Build the archive. Move and unpack it away from the original sibling shared folder.
  3. Open the relocated project. The poster is missing and returns 404. A plain poster.svg control is copied and keeps working.

The real cloud render <project> --dry-run --json reports the same two-file archive for the encoded case, with no error.

Expected behavior

The archive includes the sibling poster and rewrites its reference to a URL that resolves to that copied file.

Actual behavior

Only index.html and assets/clip.mp4 are included. The HTML retains the unresolved ../shared/poster%20art.svg. Local files with spaces and non-ASCII names are valid and load before packaging.

Environment

Reproduced on main f21c3e3e7e8387ec9638cac2179f39c630ffabcf, Linux x64, Bun 1.4.4, Node 26.11.1, FFmpeg n9.0.2. Relocated browser checks used Chrome Headless Shell 152.0.7928.2.

The integrity-verified published [email protected] archive helper also omits encoded external src assets. That release predates #5309's poster scanning, so its plain poster behavior is not a passing control for this report.

Additional context

#1160 introduced external asset localization and #5309 added poster scanning. This is a URL decoding failure in the existing resolver, distinct from proxy baking and media offloading. No authenticated publish or cloud upload was needed to reproduce it.

Activity

  1. miguel-heygen commented on Oct 25, 2026

    @miguel-heygen
    Collaborator

    Thanks for the detailed report. I can reproduce this with the published 0.8.193: hyperframes cloud render <project> --dry-run --json packs only index.html when the image is referenced as ../shared/poster%20art.svg, while the same setup with a plain file name also packs _ext/shared/plain.svg.

    The cause is tryResolveExternal in packages/cli/src/utils/publishProject.ts. It resolves the raw attribute value as a file path without decoding it, so it looks for a file literally named poster%20art.svg, finds nothing, and leaves the reference unchanged. Both publish and cloud render build their archive through that function.

    #6266 fixes it there, so that PR is the one to follow. A related case it does not cover yet: an external reference with a query or fragment, such as poster.svg?v=2 or icons.svg#play, is also left out of the archive. I've noted that on the PR.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions