vercel/next.js · critical · syn::Error

serialization = "hash" only makes sense with cell = "compare

Error message

serialization = "hash" only makes sense with cell = "compare" (or default)

What it means

This is a compile-time error emitted by the #[turbo_tasks::value] proc-macro. serialization = "hash" persists only a hash of the value so post-eviction reads can skip re-derivation by comparing hashes — which only works when cells are deduplicated by value comparison (cell = "compare", the default). Combining serialization = "hash" with cell = "keyed" or cell = "new" is contradictory, so the macro refuses to compile.

Source

Thrown at turbopack/crates/turbo-tasks-macros/src/value_macro.rs:282

pub fn value(args: TokenStream, input: TokenStream) -> TokenStream {
    let item = parse_macro_input!(input as Item);
    let ValueArguments {
        serialization_mode,
        evict_mode,
        shared,
        cell_mode,
        manual_eq,
        manual_hash,
        transparent,
        operation,
        task_input,
    } = parse_macro_input!(args as ValueArguments);

    // `serialization = "hash"` only makes sense with `cell = "compare"` (the default).
    if matches!(serialization_mode, SerializationMode::Hash)
        && !matches!(cell_mode, CellMode::Compare)
    {
        return syn::Error::new(
            proc_macro2::Span::call_site(),
            "serialization = \"hash\" only makes sense with cell = \"compare\" (or default)",
        )
        .to_compile_error()
        .into();
    }

    // `hash = "manual"` only makes sense with `serialization = "hash"`.
    if manual_hash && !matches!(serialization_mode, SerializationMode::Hash) {
        return syn::Error::new(
            proc_macro2::Span::call_site(),
            "hash = \"manual\" only makes sense with serialization = \"hash\"",
        )
        .to_compile_error()
        .into();
    }

    // `evict = "last"` only makes sense for `serialization = "skip"`: it

View on GitHub (pinned to 0ae8c72462)

Solutions

  1. Remove the cell override so it defaults to "compare": #[turbo_tasks::value(serialization = "hash")].
  2. Or explicitly set cell = "compare" if you want it documented.
  3. If you genuinely need cell = "new" or "keyed", change serialization to "auto", "custom", or "skip" instead of "hash".

Example fix

// before
#[turbo_tasks::value(serialization = "hash", cell = "new")]
struct Foo { ... }

// after
#[turbo_tasks::value(serialization = "hash")] // cell defaults to "compare"
struct Foo { ... }
Defensive patterns

Strategy: validation

Prevention

When it happens

Trigger: Annotating a value type with both serialization = "hash" and a non-compare cell mode, e.g. #[turbo_tasks::value(serialization = "hash", cell = "new")] or cell = "keyed".

Common situations: A Turbopack contributor adding a new value type and copying attributes from another type without understanding the serialization/cell interaction; refactoring a type's cell mode while leaving serialization = "hash".

Related errors


AI-assisted analysis of vercel/next.js@0ae8c72462 (2026-08-06). Data as JSON: /api/errors/c8162676d9fb245b. Report an issue: GitHub.