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

  1. Make keys unique per emission (append a counter, span hash, or file id)
  2. Track already-emitted keys in your own HashSet and skip duplicates before calling experimental_emit
  3. Restructure so each capture() scope emits each key at most once (emit once per file, not per node)
  4. 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

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


AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17). Data as JSON: /api/errors/2bf1128d1da762c0. Report an issue: GitHub.