getzola/zola · error

Couldn't get filename from page asset

Error message

Couldn't get filename from page asset

What it means

Site::copy_assets copies page asset files, computing each asset's destination relative to the parent path via Path::strip_prefix. It panics with this expect when an asset path does not start with the given parent prefix, meaning the asset lies outside the page's content directory.

Solutions

  1. Ensure every asset passed with a page lives under that page's directory (same content dir).
  2. Canonicalize both parent and asset paths (fs::canonicalize / std::path::absolute) before the strip so symlinks don't break the prefix.
  3. Check for path separator or case mismatches (Windows vs Unix) between parent and asset.
  4. Copy out-of-page assets via a static/ path instead of page-attached assets.

Example fix

// before
asset_path.strip_prefix(parent).expect("Couldn't get filename from page asset")
// after
let canon_parent = parent.canonicalize()?;
let canon_asset = asset_path.canonicalize()?;
canon_asset.strip_prefix(&canon_parent).expect("asset outside page dir")
Defensive patterns

Strategy: validation

Validate before calling

// Validate every asset is under the page dir before building
for asset in assets {
    let p = std::fs::canonicalize(asset.as_ref())?;
    assert!(p.starts_with(std::fs::canonicalize(parent)?), "asset {:?} outside page dir {:?}", p, parent);
}

Type guard

fn asset_under_parent(asset: &Path, parent: &Path) -> bool {
    let (a, p) = (asset.canonicalize().ok(), parent.canonicalize().ok());
    match (a, p) { (Some(a), Some(p)) => a.starts_with(p), _ => false }
}

Try / catch

std::panic::catch_unwind(|| copy_assets(...)).map_err(|_| anyhow!("asset path outside page directory"))?

Prevention

When it happens

Trigger: Calling site building/copy logic where a page's assets slice contains a path that is not a descendant of parent — e.g. a manually registered asset path outside the .md file's directory, or a normalized/aliased path that diverges from parent.

Common situations: Symlinked content directories where canonicalization changes the prefix; custom page/asset wiring that passes absolute or ../ paths; cross-platform path separator mismatches when parent was built differently from asset_path.

Related errors


AI-assisted analysis of getzola/zola@61d3082821 (2026-09-03). Data as JSON: /api/errors/d233d4e76749666c. Report an issue: GitHub.

Appendix: source

Thrown at components/site/src/lib.rs:709

        let mut imageproc =
            self.imageproc.lock().expect("Couldn't lock imageproc (process_images)");
        imageproc.prune()?;
        imageproc.do_process()
    }

    /// Deletes the `public` directory if it exists and the `preserve_dotfiles_in_output` option is set to false,
    /// or if set to true: its contents except for the dotfiles at the root level.
    pub fn clean(&self) -> Result<()> {
        clean_site_output_folder(&self.output_path, self.config.preserve_dotfiles_in_output)
    }

    fn copy_assets(&self, parent: &Path, assets: &[impl AsRef<Path>], dest: &Path) -> Result<()> {
        for asset in assets {
            let asset_path = asset.as_ref();
            copy_file_if_needed(
                asset_path,
                &dest.join(
                    asset_path.strip_prefix(parent).expect("Couldn't get filename from page asset"),
                ),
                self.config.hard_link_static,
            )?;
        }

        Ok(())
    }

    /// Deletes the `public` directory (only for `zola build`) and builds the site
    pub fn build(&self) -> Result<()> {
        let mut start = Instant::now();
        // Do not clean on `zola serve` otherwise we end up copying assets all the time
        if self.build_mode == BuildMode::Disk {
            self.clean()?;
        }
        start = log_time(start, "Cleaned folder");

        // Generate/move all assets before markdown any content

View on GitHub (pinned to 61d3082821)