karatelabs/karate · error · JsErrorException
assignment to constant
Error message
assignment to constant: ${key} What it means
Karate's JS engine throws this TypeError when script code assigns to a variable that was declared with `const` and already initialized. Per ECMAScript semantics, const bindings are single-assignment; reassignment is a static violation that the engine detects at runtime in its name-resolution path (CoreContext.update). The name in the message is the identifier the script tried to rebind.
Solutions
- Rename the assignment target or change the declaration from const to let/var so rebinding is allowed
- If mutation is intentional, declare a new const for the new value instead of reassigning (e.g. `const y = x + 1`)
- If the constant lives in karate config (karate-config.js), move the mutation to a non-const variable
- Refactor mutation into an object property or array element — property writes are not const-binding writes
Example fix
// before
const limit = 10;
for (limit = 0; limit < n; limit++) { ... }
// after
const limit = 10;
for (let i = 0; i < n && i < limit; i++) { ... } Defensive patterns
Strategy: validation
Validate before calling
// before assigning, check the binding is not const in your script discipline: // prefer let for anything mutated; in karate JS blocks use `let` for counters let limit = 10;
Type guard
function isMutableBinding(name, scope) { return typeof scope[name] !== 'undefined' && scope.__consts == null || (scope.__consts && !scope.__consts.has(name)); } Try / catch
try { x = 2; } catch (e) { if (String(e).includes('assignment to constant')) { /* use a new variable */ } else { throw e; } } Prevention
- Default to let; use const only for values proven never reassigned
- Enable linting (no-const-assign rule) in scripts under version control
- Avoid reusing config constants as loop counters
- Mutate object/array contents instead of rebinding const names
When it happens
Trigger: Script code does `const x = 1; x = 2;`, or assigns to a const via `set`/compound assignment (`x += 1`, `x++` flows through update/compoundRefByName/setByName which all funnel into update). Any path resolving a variable by name to a CONST-scoped, initialized slot and calling update hits this.
Common situations: Porting JS that used `var`/`let` to `const` but still mutates loop counters or accumulators; reusing a config constant as a temp variable inside karate script expressions; accidental shadowing mistakes where the developer thinks they declare a new variable but only assign.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- assignment to constant
- Cannot set property ' ' which has only a getter
- Generator is already running
- groupBy called with null or undefined items
- groupBy callback is not a function
AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12).
Data as JSON: /api/errors/13f84fafb01bb364.
Report an issue: GitHub.
Appendix: source
Thrown at karate-js/src/main/java/io/karatelabs/js/CoreContext.java:570
if (idx >= 0 && frame[idx] != SlotTable.UNDECLARED) {
updateSlot(idx, value, node);
return;
}
// undeclared: fall through — a pre-declaration write resolves an
// outer/global binding (or creates an implicit global), as today
}
BindingSlot s = resolve(key);
if (s == null) {
if (strict) {
// Strict mode forbids the sloppy implicit-global creation:
// assigning to an unresolvable name is a ReferenceError.
throw JsErrorException.referenceError(key + " is not defined");
}
assignImplicitGlobal(key, value, node);
return;
}
if (s.scope == BindScope.CONST && s.initialized) {
throw JsErrorException.typeError("assignment to constant: " + key);
}
// NamedEvaluation (§13.15.2): when RHS is an anonymous function expression and
// LHS is an IdentifierRef, set fn.name from the identifier. Mirrors the
// declare-path hook above. Skipped for already-named functions so e.g.
// `g = someNamedFn` does not clobber the original name.
if (value instanceof JsFunction fn && (fn.name == null || fn.name.isEmpty())) {
fn.name = key;
}
Object oldValue = s.value;
s.initialized = true;
// Unified write — works whether the Slot lives in this context's
// bindings, in capturedBindings, in an outer context, or in root.
// Sibling closures sharing the same Slot reference see the new
// value immediately.
s.value = value;
if (root.listener != null) {
root.listener.onBind(BindEvent.assign(key, value, oldValue, this, node));
}View on GitHub (pinned to a22eb90246)