thedotmack/claude-mem · error · Error

strip introduced ${afterErrs - beforeErrs} new parse error(s

Error message

strip introduced ${afterErrs - beforeErrs} new parse error(s); refusing to write

What it means

Thrown by stripJsLike() as a safety guard: after removing comment ranges, it re-parses the result with the TypeScript compiler and compares parse-diagnostic counts. If stripping introduced any new parse errors, it refuses to write the file. This prevents a comment-stripping tool from silently corrupting source by, e.g., merging two tokens that a comment had separated (ASI/regex/string-edge cases).

Source

Thrown at scripts/strip-comments.ts:161

    for (const r of trailing) addRange(r);
    ts.forEachChild(node, visitNode);
  }
  function addRange(r: ts.CommentRange): void {
    const key = `${r.pos}-${r.end}`;
    if (seen.has(key)) return;
    seen.add(key);
    const text = source.slice(r.pos, r.end);
    if (isDirectiveJs(text)) return;
    ranges.push([r.pos, r.end]);
  }

  visitNode(sf);
  const out = spliceRanges(source, ranges);

  const after = ts.createSourceFile('check', out, ts.ScriptTarget.Latest, true, kind);
  const afterErrs = parseDiagnosticsCount(after);
  if (afterErrs > beforeErrs) {
    throw new Error(`strip introduced ${afterErrs - beforeErrs} new parse error(s); refusing to write`);
  }
  return out;
}

function collapseBlankLines(s: string): string {
  return s.replace(/(?:[ \t]*\n){3,}/g, '\n\n');
}

function spliceRanges(source: string, ranges: Array<[number, number]>): string {
  ranges.sort((a, b) => a[0] - b[0]);
  let out = source;
  for (let i = ranges.length - 1; i >= 0; i--) {
    const [s, e] = ranges[i];
    let removeStart = s;
    let removeEnd = e;
    let lineStart = s;
    while (lineStart > 0 && (out[lineStart - 1] === ' ' || out[lineStart - 1] === '\t')) {
      lineStart--;

View on GitHub (pinned to d768ba3643)

Solutions

  1. Add the @strip-comments-keep marker at the top of the offending file so it is skipped entirely.
  2. Inspect the reported file: re-parse mentally what the comment was separating and either keep that comment or add a semicolon/parenthesization so removal is safe.
  3. Run the script with --dry-run --verbose to identify which file fails, then exclude it via SKIP_PATHS/SKIP_BASENAMES if it is generated/vendored.
  4. Report a parser-edge-case bug if the file is ordinary TS with no regex/JSX tricks.

Example fix

// before — file trips the guard after stripping
export const a = 1
// some comment
const b = 2
// after — add the keep marker at top of file so it is skipped
// @strip-comments-keep
export const a = 1
Defensive patterns

Strategy: try-catch

Validate before calling

// skip files explicitly marked to keep
if (KEEP_MARKER.test(original.slice(0, 4096))) { stats.skipped++; return; }

Try / catch

try {
  stripped = stripJsLike(original, ext);
} catch (e) {
  stats.errors.push(`${relPath}: ${(e as Error).message}`);
  return; // do not write a possibly-corrupt file
}

Prevention

When it happens

Trigger: Removing a line comment that was acting as ASI separation between two tokens, turning them into one. Removing a block comment adjacent to a regex literal so the `/` is reinterpreted. Removing comments inside or adjacent to template literals where TypeScript' comment-range detection spanned into expression text. Edge cases in ts.getLeadingCommentRanges/getTrailingCommentRanges on JSX or decorator syntax.

Common situations: Running strip-comments across a large codebase and one unusual file (heavy regex, dense JSX, decorator metadata) trips the parser. A file where a `//` appears inside a string but was mis-detected as a comment range. New TypeScript syntax the parser handles differently after comment removal.

Understand the failure class

Related errors


AI-assisted analysis of thedotmack/claude-mem@d768ba3643 (2026-08-12). Data as JSON: /api/errors/9cdb645f13841bb4. Report an issue: GitHub.