vercel/next.js · critical · syn::Error
evict = "last" is only valid with serialization = "skip"
Error message
evict = "last" is only valid with serialization = "skip"
What it means
Compile-time error from the #[turbo_tasks::value] proc-macro. evict = "last" marks a cell as expensive to re-derive so the eviction policy prefers evicting cheaper cells first — but re-derivation is only the recovery path for serialization = "skip". Persistable cells restore from disk and HashOnly cells short-circuit on an unchanged hash, so evict = "last" with those modes is meaningless and the macro rejects it. (evict = "never" is allowed with any serialization mode.)
Source
Thrown at turbopack/crates/turbo-tasks-macros/src/value_macro.rs:311
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
// says "re-deriving this cell is expensive", and re-derivation is the
// recovery path only for skip mode. Persistable cells restore from disk
// (predictable cost), HashOnly cells short-circuit on unchanged hash.
//
// `evict = "never"` is allowed with any serialization mode — a value
// type can be persistable AND hold session-scoped state that must not
// leave memory (e.g. `DiskFileSystem` carrying file watchers).
if matches!(evict_mode, EvictMode::Last)
&& !matches!(serialization_mode, SerializationMode::Skip)
{
return syn::Error::new(
proc_macro2::Span::call_site(),
"evict = \"last\" is only valid with serialization = \"skip\"",
)
.to_compile_error()
.into();
}
let mut struct_attributes = vec![quote! {
#[derive(
turbo_tasks::ShrinkToFit,
turbo_tasks::trace::TraceRawVcs,
turbo_tasks::NonLocalValue
)]
#[shrink_to_fit(crate = "turbo_tasks::macro_helpers::shrink_to_fit")]
}];
let mut inner_type = None;
if transparent {View on GitHub (pinned to 0ae8c72462)
Solutions
- Set serialization = "skip" to make evict = "last" meaningful: #[turbo_tasks::value(serialization = "skip", evict = "last")].
- Or remove evict = "last" if the value is persistable and eviction ordering is not needed.
- If you need non-eviction for a persistable type, use evict = "never" instead, which is valid with any serialization mode.
Example fix
// before
#[turbo_tasks::value(serialization = "auto", evict = "last")]
struct HeavyCache { ... }
// after
#[turbo_tasks::value(serialization = "skip", evict = "last")]
struct HeavyCache { ... } Defensive patterns
Strategy: validation
Prevention
- Only set evict = "last" together with serialization = "skip".
- For persistable types that must not be evicted, use evict = "never" (valid with any serialization mode).
- Remember evict hints only affect re-derivation cost, which is the recovery path solely for skip mode.
When it happens
Trigger: Annotating a value type with evict = "last" and a serialization mode other than "skip", e.g. #[turbo_tasks::value(serialization = "auto", evict = "last")] or serialization = "hash".
Common situations: A contributor marking a heavy-to-compute value as evict = "last" without realizing the hint only applies to skip-serialization types; copy-pasting attributes across types with different serialization modes.
Related errors
- serialization = "hash" only makes sense with cell = "compare
- hash = "manual" only makes sense with serialization = "hash"
- gave up applying effects after {MAX_RETRIES} retries; repeat
AI-assisted analysis of vercel/next.js@0ae8c72462 (2026-08-06).
Data as JSON: /api/errors/9f86a33702b38e14.
Report an issue: GitHub.