rwf2/Rocket · error · minijinja::Error

InvalidOperation

InvalidOperation

Error message

template read failed

What it means

This error is produced by rocket_dyn_templates' MiniJinja engine when a template name resolves to a registered path on disk, but reading that file fails with an I/O error other than NotFound. Note that a missing template is deliberately treated as Ok(None) (minijinja then reports 'template not found'), so this error means the path exists but cannot be read, e.g. a permission error or the path points at a directory. It is wrapped in minijinja's ErrorKind::InvalidOperation with the underlying io::Error as its source, and surfaces when a template is loaded/rendered.

Source

Thrown at contrib/dyn_templates/src/engine/minijinja.rs:29

    const EXT: &'static str = "j2";

    fn init<'a>(templates: impl Iterator<Item = (&'a str, &'a Path)>) -> Option<Self> {
        let _templates = Arc::new(templates
            .map(|(k, p)| (k.to_owned(), p.to_owned()))
            .collect::<HashMap<_, _>>());

        let templates = _templates.clone();
        let mut env = Environment::new();
        env.set_loader(move |name| {
            let Some(path) = templates.get(name) else {
                return Ok(None);
            };

            match std::fs::read_to_string(path) {
                Ok(result) => Ok(Some(result)),
                Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
                Err(e) => Err(
                    Error::new(ErrorKind::InvalidOperation, "template read failed").with_source(e)
                ),
            }
        });

        let templates = _templates.clone();
        env.set_auto_escape_callback(move |name| {
            templates.get(name)
                .and_then(|path| path.to_str())
                .map(minijinja::default_auto_escape_callback)
                .unwrap_or(AutoEscape::None)
        });

        Some(env)
    }

    fn render<C: Serialize>(&self, template: &str, context: C) -> Option<String> {
        let Ok(templ) = self.get_template(template) else {
            error!(template, "requested template does not exist");

View on GitHub (pinned to 3a54d079ae)

Solutions

  1. Check read permissions on the file and every parent directory for the server user: sudo -u www-data cat templates/index.html
  2. Verify the path is a regular file, not a directory: ls -ld $(find temp_dir -name '<template>')
  3. Confirm temp_dir/templates dir config in Rocket.toml points at the directory you expect (relative to the CWD the binary is launched from)
  4. If running in a container, ensure the template directory is actually copied/mounted and not masked by a volume

Example fix

# before (file unreadable by server user)
$ chmod 600 templates/index.html   # server runs as www-data

# after
$ chmod 644 templates/index.html
$ find templates/ -type d -exec chmod a+rx {} \;
Defensive patterns

Strategy: try-catch

Validate before calling

// before rendering, confirm every needed template is readable
use std::fs;
fn templates_readable(dir: &std::path::Path, names: &[&str]) -> std::io::Result<()> {
    for n in names {
        let p = dir.join(format!("{n}.hbs")); // or .html for minijinja
        let meta = fs::metadata(&p)?;
        if meta.is_dir() { return Err(std::io::Error::new(std::io::ErrorKind::IsADirectory, p.display().to_string())); }
        fs::File::open(&p)?; // proves readability under current user
    }
    Ok(())
}

Try / catch

let rendered = match tmpl.render("index", &ctx) {
    Ok(body) => body,
    Err(e) if e.kind() == minijinja::ErrorKind::InvalidOperation => {
        error_!("template unreadable: {e}");
        return Status::InternalServerError;
    },
    Err(e) => {
        error_!("render failed: {e}");
        return Status::InternalServerError;
    },
};

Prevention

When it happens

Trigger: Calling Template::render / render_template on a route after rocket_dyn_templates is attached, where the template glob matched a file that later becomes unreadable: chmod 000 or chown to another user on a .html file, a template path that is actually a directory, or an unreadable path inside a symlinked dir. Also occurs if the process' runtime user (systemd service, container) lacks read permission on the template directory.

Common situations: Deploying behind a service user with different file ownership than the build/deploy user; Docker images that COPY templates with restrictive modes; NFS-mounted template dirs; a stale glob (templ_dir changed in Rocket.toml but old files remain as directories or broken symlinks).

Related errors


AI-assisted analysis of rwf2/Rocket@3a54d079ae (2026-08-16). Data as JSON: /api/errors/43a65785bc895ca1. Report an issue: GitHub.