`setup defaults --agent claude` silently skips Claude Code: JSON comment stripper corrupts `//` inside permission strings
- Dominant language
- TypeScript
- Stars
- 631
- Forks
- 100
- Avg merge
- 5h 9m
- Merged PRs (30d)
- 25
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-cli` 1.23.3 (via `npx -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`:
```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`):
```js
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:
```js
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:
```diff
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`](https://www.npmjs.com/package/jsonc-parser) (used by VS Code for this exact problem) handles both the read and the write:
```js
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`:
```json
"permissions": {
"deny": ["WebSearch", "WebFetch"]
}
```
Note that `setup defaults --undo` fails the same way, so undoing also has to be done by hand.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start in src/utils/web-defaults.ts, especially removeJsonComments(), configureClaudeDefaults(), and configureWebDefaults(). Run the reported firecrawl setup defaults --agent claude reproduction with a Read(//c/Users/...) permission rule, then verify valid settings remain parseable, the Claude deny entry is handled, and skipped agents do not produce an unconditional success result.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- cli
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 74/100