swc-project/swc · error · anyhow::Error
invalid source context byte range: {old_start}..{old_end}
Error message
invalid source context byte range: {old_start}..{old_end} What it means
Thrown by the Node binding's AST round-trip path: when a serialized program envelope is passed back into native code, rebase_program_spans restores the original SourceMap positions and rejects an inverted byte range (startPos > endPos). The sourceContext object normally originates from SWC's own serialize_program, so a valid pipeline never produces this; it fires when the envelope JSON was hand-built or patched with wrong positions. Note that the earlier checked_sub guard in prepare_program_with_context usually intercepts the same condition first with a message that lacks the range.
Source
Thrown at bindings/binding_core_node/src/ast_context.rs:111
rebase_program_spans(&mut program, &source_context, &fm)
.context("failed to rebase AST spans to restored source context")?;
Ok((fm, program))
}
fn rebase_program_spans(
program: &mut Program,
source_context: &ProgramSourceContext,
fm: &SourceFile,
) -> Result<(), Error> {
let old_start = source_context.start_pos;
let old_end = source_context.end_pos;
let new_start = fm.start_pos.0;
let new_end = fm.end_pos.0;
if old_start > old_end {
bail!("invalid source context byte range: {old_start}..{old_end}");
}
program.visit_mut_with(&mut SpanRebaser {
old_start,
old_end,
new_start,
new_end,
});
Ok(())
}
impl ProgramSourceContext {
fn file_name(&self) -> FileName {
match &self.real_filename {
Some(filename) => FileName::Real(filename.into()),
None => FileName::Anon,
}View on GitHub (pinned to 5176682b65)
Solutions
- Pass the envelope JSON exactly as SWC emitted it (serialize_program output) instead of reconstructing sourceContext by hand
- If you must build it: keep startPos <= endPos and make endPos - startPos equal the UTF-8 byte length of source (new TextEncoder().encode(source).length)
- Emit and consume envelopes with the same @swc/core version on both sides of the JS/native boundary
- Check whether you actually hit the sibling guard in prepare_program_with_context ('invalid source context byte range' without the a..b suffix, or the 'source length changed' bail) and fix the length mismatch instead
Example fix
// before
const envelope = {
program,
sourceContext: { source, realFilename: null, startPos: end, endPos: start },
};
// after - range ordered, and matching the UTF-8 byte length of source
const bytes = new TextEncoder().encode(source).length;
const envelope = {
program,
sourceContext: { source, realFilename: null, startPos: 0, endPos: bytes },
}; Defensive patterns
Strategy: validation
Validate before calling
const enc = new TextEncoder();
function assertValidSourceContext(ctx) {
if (!Number.isInteger(ctx.startPos) || !Number.isInteger(ctx.endPos))
throw new TypeError('startPos/endPos must be integers');
if (ctx.startPos > ctx.endPos)
throw new RangeError(`startPos ${ctx.startPos} > endPos ${ctx.endPos}`);
if (ctx.endPos - ctx.startPos !== enc.encode(ctx.source).length)
throw new RangeError('range must equal the UTF-8 byte length of source');
}
// call before passing the envelope to the native binding
assertValidSourceContext(envelope.sourceContext); Type guard
type SourceContext = { source: string; startPos: number; endPos: number };
const hasValidSourceContext = (
env: { sourceContext?: SourceContext },
): boolean => {
const c = env.sourceContext;
return (
!!c &&
Number.isInteger(c.startPos) &&
Number.isInteger(c.endPos) &&
c.startPos <= c.endPos
);
}; Try / catch
try {
const res = transformWithAst(envelope);
} catch (e) {
if (String(e?.message).includes('invalid source context byte range')) {
throw new Error('AST envelope has an inverted byte range - regenerate it from SWC output instead of hand-editing');
}
throw e;
} Prevention
- Never hand-write sourceContext; echo back the envelope a previous SWC call returned
- Compute lengths with TextEncoder (UTF-8 bytes), never string .length (UTF-16 units)
- Pin the same @swc/core version on the producing and consuming side of the AST boundary
When it happens
Trigger: Calling a native SWC API that accepts a serialized program envelope (ProgramEnvelope with camelCase sourceContext: {source, realFilename, startPos, endPos}) where startPos > endPos, e.g. swapping the two positions or recomputing them from character offsets instead of byte offsets.
Common situations: Custom codemod or AST-explorer tooling that regenerates envelope JSON and computes start/end positions itself; feeding an envelope produced by a different @swc/core version whose context shape changed; counting UTF-16 code units (JS string length) instead of UTF-8 bytes against a range built from byte offsets.
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
- Unknown statement type: ${(stmt as any).type
- Method visitTsType not implemented.
AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17).
Data as JSON: /api/errors/51e6e4fdbc5c735a.
Report an issue: GitHub.