Repository navigation
setup defaults --agent claude silently skips Claude Code: JSON comment stripper corrupts // inside permission strings #219
Description
Summary
firecrawl setup defaults --agent claude reports
! Skipped Claude Code settings because settings.json is not valid JSON
against a settings.json that is valid JSON. The cause is the regex comment-stripper in configureClaudeDefaults, which deletes from any // to end-of-line — including // that occurs inside a JSON string literal.
This is not a rare edge case on Windows. Claude Code writes local-path permission rules in the form Read(//c/Users/...), so the leading // appears in the file as soon as the user approves any path-scoped read or edit. One such rule is enough to make the whole file unparseable to the CLI, permanently.
Environment
firecrawl-cli1.23.3 (vianpx -y firecrawl-cli@1)- Windows 11, Node v24.13.0
Reproduction
Two settings.json files, both valid JSON, differing by one line.
Case A — ~/.claude/settings.json:
{
"permissions": {
"allow": [
"Read(//c/Users/someone/projects/**)"
]
},
"model": "opus"
}Case B — identical, with that rule replaced by "Bash(git status)".
Run firecrawl setup defaults --agent claude against each:
| result | |
|---|---|
| A | ! Skipped Claude Code settings because settings.json is not valid JSON — no deny written |
| B | ✓ Disabled Claude Code native WebSearch/WebFetch — deny written correctly |
JSON.parse() accepts both files as-is.
Root cause
removeJsonComments() in src/utils/web-defaults.ts (observed in dist/utils/web-defaults.js:28):
function removeJsonComments(content) {
return content
.replace(/\/\*[\s\S]*?\*\//g, '')
.replace(/(^|[^:\\])\/\/.*$/gm, '$1');
}The [^:\\] guard exempts :// (protecting URLs) and escaped slashes, but the // in "Read(//c/Users/...)" is preceded by (. The line is truncated to "Read(, leaving an unterminated string, and the subsequent JSON.parse in configureClaudeDefaults throws:
Bad control character in string literal in JSON at position 988
The catch then reports the file as invalid and skips the agent.
Because the stripper runs over the entire file before parsing, a single // anywhere poisons the parse for every key.
Two related problems in the same function
1. The comment support is self-defeating. The write path is:
await writeText(filePath, `${JSON.stringify(nextConfig, null, 2)}\n`);The file is rebuilt from the parsed object, so any comments the stripper removed in order to parse are silently discarded on write — along with the user's key order and formatting. The stripper exists to tolerate comments, and the writer destroys them.
2. The success line prints unconditionally. configureWebDefaults can skip every agent and the command still ends with:
Firecrawl is now the default web provider for supported AI agents.
and exit code 0. That is what masked the failure initially — the warning scrolls past and the closing line reads as success. It also makes the failure invisible in CI.
Suggested fixes
Minimal — try strict JSON first
Valid JSON never reaches the regex. Verified working against Case A above:
if (existing && existing.trim()) {
try {
- config = JSON.parse(removeJsonComments(existing));
+ try {
+ config = JSON.parse(existing);
+ }
+ catch {
+ config = JSON.parse(removeJsonComments(existing));
+ }
}
catch {
return { /* skipped */ };
}
}Proper — use a string-aware JSONC parser
Regex stripping cannot be made correct; [^:\\] was already a patch for https://, and (// is the next hole. Any // inside a string value will keep reproducing this. jsonc-parser (used by VS Code for this exact problem) handles both the read and the write:
import { parse, modify, applyEdits } from 'jsonc-parser';
const config = parse(existing) ?? {};
// ...compute nextDeny...
const edits = modify(existing, ['permissions', 'deny'], nextDeny, {
formattingOptions: { insertSpaces: true, tabSize: 2 },
});
await writeText(filePath, applyEdits(existing, edits));modify + applyEdits also resolves problem 1 — surgical edits preserve comments, key order, and formatting instead of rewriting the document.
Reporting
Gate the closing message on results.some(r => !r.skipped), and return a non-zero exit code when an agent was skipped, so the failure is visible to users and to CI.
Workaround
Add the entries by hand to ~/.claude/settings.json:
"permissions": {
"deny": ["WebSearch", "WebFetch"]
}Note that setup defaults --undo fails the same way, so undoing also has to be done by hand.
Activity
johnsnow92 commentedon Sep 22, 2026
The
/* … */half of the sameremoveJsonComments()regex fails in a worse way than the//case above. It doesn't makesettings.jsonunparseable. It produces valid JSON with different content, sosetup defaultswrites it back, reports success, and silently removes a deny rule.Environment
firecrawl-cli1.24.3 (current npmlatest)Reproduction
A temp
HOMEkeeps real settings untouched:Expected
Actual: the command prints
✓ Disabled Claude Code native WebSearch/WebFetch, and the file contains:The lazy
/\/\*[\s\S]*?\*\//gmatches from the/*in**/*.keyto the*/in**/secrets/. That deletes the text between them and merges two deny rules into one pattern that protects neither path as written. Because the result still parses, nothing flags it.--undoruns the same stripper. It removesWebSearch/WebFetchbut cannot restore the lost rule. Afterwards the file holds only["Read(**secrets/**)"], so undoing doesn't repair the damage.Why it matters
Globs like
**/*.key,**/*.pemand**/secrets/**are the standard way to deny reads of credentials in Claude Code. Any file with a/*in one string and a*/later in any string, even in another section, is affected. The failure is silent and weakens protections the user configured deliberately.Suggested fix
JSON.parsefirst and strip comments only if that fails; Claude Code writes strict JSON. (A string-aware scanner, as in fix(web-defaults): safely parse JSON with strings containing slashes … #247, also works.)permissions.deny, and refuse to write otherwise.Workaround: add
"WebSearch"and"WebFetch"topermissions.denyby hand.setup defaults --agent codexis unaffected, because it edits one TOML line.