swc-project/swc · error
Key already set. Previous value: {previous:?}
Error message
Key already set. Previous value: {previous:?} What it means
experimental_emit in swc_transform_common::output pushes a key/value pair into the thread-local RefCell<FxHashMap> installed by capture(), forwarding metadata from a transform to the caller. On the native (non-wasm-plugin) path it inserts the key and panics if a value was already stored under it, refusing to silently overwrite a previous emission.
Source
Thrown at crates/swc_transform_common/src/output.rs:56
)
.expect("Should able to serialize String");
let (ptr, len) = diag.as_ptr();
unsafe {
__emit_output(ptr as u32, len as u32);
}
}
/// (Experimental) Emits a value to the JS caller.
///
/// This is not stable and may be removed in the future.
#[cfg(not(all(feature = "plugin-mode", target_arch = "wasm32")))]
pub fn experimental_emit(key: String, value: String) {
OUTPUT.with(|output| {
let previous = output.borrow_mut().insert(key, value);
if let Some(previous) = previous {
panic!("Key already set. Previous value: {previous:?}");
}
});
}
View on GitHub (pinned to 5176682b65)
Solutions
- Make keys unique per emission (append a counter, span hash, or file id)
- Track already-emitted keys in your own HashSet and skip duplicates before calling experimental_emit
- Restructure so each capture() scope emits each key at most once (emit once per file, not per node)
- Avoid relying on experimental_emit at all; it is explicitly marked unstable
Example fix
// before
experimental_emit("diag".into(), first.clone());
experimental_emit("diag".into(), second.clone()); // panics: Key already set
// after
experimental_emit("diag-0".into(), first.clone());
experimental_emit("diag-1".into(), second.clone()); Defensive patterns
Strategy: validation
Validate before calling
use std::collections::HashSet;
let mut emitted: HashSet<String> = HashSet::new();
fn emit_once(emitted: &mut HashSet<String>, key: &str, value: String) {
let unique_key = if emitted.contains(key) {
format!("{key}-{}", emitted.len())
} else {
key.to_string()
};
emitted.insert(unique_key.clone());
swc_transform_common::output::experimental_emit(unique_key, value);
} Try / catch
let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
experimental_emit(key.clone(), value.clone())
}));
if res.is_err() {
// duplicate key: log and continue without aborting the transform
log::warn!("experimental_emit skipped duplicate key {key}");
} Prevention
- Never call experimental_emit with a constant key from per-node visitor code
- Track emitted keys in your own HashSet and uniquify (counter/span/file id) before emitting
- Emit at most once per capture() scope per key
- Remember the API is experimental: wrap all uses so it can be swapped out
When it happens
Trigger: Calling experimental_emit with the same key twice within one capture() scope: a visitor emitting under a fixed key per visited node, a transform emitting per-file metadata while several files are processed inside one captured closure, or the same transform pass running twice.
Common situations: Writing SWC plugins/tools that emit debug or metadata with constant keys like "log" or "result"; emitting inside visit_* methods that fire multiple times; treating this experimental API as an append-only log without uniquing keys.
Related errors
- Invalid attempt to iterate non-iterable instance. In order t
- Duplicated methods (${element.key}) can't be decorated.
- Decorators can't be placed on different accessors with for t
- Duplicated element (${element.key})
- Duplicated element (${elements[j].key})
AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17).
Data as JSON: /api/errors/2bf1128d1da762c0.
Report an issue: GitHub.