[BUG] Config file corruption with "JSON Parse error: Unterminated string" in Docker container
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
Summary
Claude Code's configuration file (~/.claude.json) is becoming corrupted in the Docker container with "JSON Parse
error: Unterminated string". This causes Claude CLI to hang waiting for interactive input, which cannot be
provided in automated/containerized environments. I am running v2.1.20
Additionally, even though I'm running claude in headless mode, i still get prompted to handle a "configruation error" which halts my code.
## Error Output
[2026-01-29 15:32:32] [WARNING] Claude config file at /home/huginn/.claude.json is corrupted: Unterminated string starting at: line 180 column 3 (char 5745) - deleting
[2026-01-29 15:32:32] [INFO] Claude output streaming: enabled
[2026-01-29 15:32:32] [INFO] [CLAUDE] Starting Claude Code session (streaming enabled)
[CLAUDE] Claude configuration file at /home/huginn/.claude.json is corrupted: JSON Parse error: Unterminated
string
[CLAUDE] The corrupted file has already been backed up.
[CLAUDE] A backup file exists at: /home/huginn/.claude.json.backup.1769553810124
[CLAUDE] You can manually restore it by running: cp "/home/huginn/.claude.json.backup.1769553810124"
"/home/huginn/.claude.json"
[CLAUDE]
[CLAUDE] ╭────────────────────────────────────────────────────────────────────╮
[CLAUDE] │ │
[CLAUDE] │ Configuration Error │
[CLAUDE] │ │
[CLAUDE] │ The configuration file at /home/huginn/.claude.json contains │
[CLAUDE] │ invalid JSON. │
[CLAUDE] │ │
[CLAUDE] │ JSON Parse error: Unterminated string │
[CLAUDE] │ │
[CLAUDE] │ Choose an option: │
[CLAUDE] │ ❯ 1. Exit and fix manually │
[CLAUDE] │ 2. Reset with default configuration │
[CLAUDE] │ │
[CLAUDE] ╰────────────────────────────────────────────────────────────────────╯
## Environment Details
Docker Setup:
- Running Claude Code in Docker container as non-root user
huginn - Config file mounted as:
~/.claude.json:/home/huginn/.claude.json(read-write) - Session directory mounted as:
~/.claude:/home/huginn/.claude(read-write)
Here are my volumes in my docker-compose.yml file.
volumes:
# Persistent state for CI fix tracking and logs
- ./state:/app/state
# Docker socket for docker/docker compose commands
- /var/run/docker.sock:/var/run/docker.sock
# Claude session data (needs write access for session files)
- ~/.claude:/home/huginn/.claude
# Claude config (needs write access)
- ~/.claude.json:/home/huginn/.claude.json
Current Mitigation (Partial):
We've implemented validation and retry logic in huginn/claude_runner.py:
_ensure_valid_claude_config()validates JSON before each Claude invocation_is_config_error_prompt()detects the interactive error prompt- Automatic retry with config reset on corruption detection
- See: [claude_runner.py:350-575](huginn/claude_runner.py:350)
However, this doesn't prevent the corruption from occurring initially.
## Root Cause Hypothesis
The "Unterminated string" JSON error suggests the file is being written incompletely. Possible causes:
- Race condition during concurrent writes: Claude CLI may be writing to config while another process reads it
- Process crash during write: If Claude CLI crashes mid-write, the JSON becomes malformed
- File locking issues: The mounted volume might not properly handle file locking between host and container
- Buffer flushing: Incomplete buffer flush during file write operations
## Impact
- Severity: High - Blocks automated workflow execution
- Frequency: Intermittent but recurring
- Recovery: Requires manual intervention or automated detection and reset (currently implemented as
workaround)
## Expected Behavior
Claude CLI should:
- Use atomic writes when updating
~/.claude.json(write to temp file, then atomic rename) - Implement proper file locking to prevent concurrent write corruption
- In non-interactive environments (no TTY), automatically reset corrupted config instead of prompting
- Validate JSON before persisting to disk
## Requested Fix
### Option 1: Atomic File Writes (Preferred)
Implement atomic writes for config updates:
```python
# Write to temporary file first
tmp_file = config_path.with_suffix('.json.tmp')
with tmp_file.open('w') as f:
json.dump(config_data, f)
f.flush()
os.fsync(f.fileno()) # Ensure data reaches disk
# Atomic rename (POSIX guarantees atomicity)
tmp_file.rename(config_path)
Option 2: File Locking
Use advisory file locking to prevent concurrent writes:
import fcntl
with config_path.open('r+') as f:
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
try:
config_data = json.load(f)
# ... modify config ...
f.seek(0)
f.truncate()
json.dump(config_data, f)
f.flush()
os.fsync(f.fileno())
finally:
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
Option 3: Non-Interactive Auto-Recovery
When running without a TTY and config is corrupted, automatically reset to default instead of prompting:
if (!process.stdout.isTTY && configIsCorrupted) {
console.error('Config corrupted in non-interactive mode - auto-resetting');
resetToDefaultConfig();
// Continue execution instead of exiting
}
Reproduction
While intermittent, corruption seems more likely when:
- Multiple Claude CLI processes run concurrently (though we use git worktrees to isolate)
- Container is stopped abruptly during Claude execution
- High-frequency Claude CLI invocations in loop mode
Workaround
Current mitigation in our code:
- Pre-validate config JSON before each Claude invocation
- Detect interactive config error prompt in Claude output
- Auto-delete corrupted config and retry once
- Exit with error if corruption persists after reset
See implementation: huginn/claude_runner.py:350-575
Additional Context
- Related to file I/O in containerized environments with mounted volumes
- May be Docker-specific (file system semantics differ from native)
- Config file structure suggests it's written frequently (possibly on every CLI invocation)
---
Labels: bug, claude-cli, config-corruption, docker
Priority: High
Component: Claude Code CLI
Version: Latest (as of 2026-01-29)
```
What Should Happen?
claude should be able to run in a docker container, mounting claude auth files so i can use my claude max plan without corrupting config files
Error Messages/Logs
[CLAUDE] Claude configuration file at /home/huginn/.claude.json is corrupted: JSON Parse error: Unterminated
string
[CLAUDE] The corrupted file has already been backed up.
[CLAUDE] A backup file exists at: /home/huginn/.claude.json.backup.1769553810124
[CLAUDE] You can manually restore it by running: cp "/home/huginn/.claude.json.backup.1769553810124"
"/home/huginn/.claude.json"
[CLAUDE]
[CLAUDE] ╭────────────────────────────────────────────────────────────────────╮
[CLAUDE] │ │
[CLAUDE] │ Configuration Error │
[CLAUDE] │ │
[CLAUDE] │ The configuration file at /home/huginn/.claude.json contains │
[CLAUDE] │ invalid JSON. │
[CLAUDE] │ │
[CLAUDE] │ JSON Parse error: Unterminated string │
[CLAUDE] │ │
[CLAUDE] │ Choose an option: │
[CLAUDE] │ ❯ 1. Exit and fix manually │
[CLAUDE] │ 2. Reset with default configuration │
[CLAUDE] │ │
[CLAUDE] ╰────────────────────────────────────────────────────────────────────╯
Steps to Reproduce
Steps to Reproduce
Note: This bug is intermittent and may require multiple attempts to reproduce. The following steps increase
the likelihood of triggering the corruption.
### Prerequisites
- Docker installed and running
- Claude Code CLI authentication token configured
- A project with the Huginn setup (or any project that runs Claude CLI in Docker repeatedly)
### Method 1: High-Frequency Execution (Most Reliable)
```bash
# 1. Set up Docker container with mounted Claude config
docker-compose.yml:
volumes:
- ~/.claude:/home/huginn/.claude
- ~/.claude.json:/home/huginn/.claude.json
# 2. Run Claude CLI repeatedly in loop mode
make run-loop LOOP_INTERVAL=1
# 3. While running, in another terminal, repeatedly read the config
while true; do
cat ~/.claude.json > /dev/null
sleep 0.1
done
# 4. Let this run for 30-60 minutes
# Corruption typically occurs within this timeframe
Method 2: Simulated Crash During Write
# 1. Start a long-running Claude CLI task in Docker
docker compose run --rm huginn --loop 45
# 2. While Claude is actively executing (watch logs for "[CLAUDE]" output):
docker compose kill -s SIGKILL huginn
# 3. Inspect the config file
cat ~/.claude.json
# If corrupted, you'll see incomplete JSON
# 4. Try to run Claude again
docker compose run --rm huginn
# Should show the corruption error prompt
Method 3: Concurrent Docker Instances
# 1. Launch multiple Claude CLI instances simultaneously
# Each sharing the same mounted config file
for i in {1..5}; do
docker compose run --rm huginn --dry-run &
done
# 2. Wait for all to complete
wait
# 3. Check config file integrity
python3 -c "import json; json.load(open('/home/user/.claude.json'))"
# If corrupted, will show JSON decode error
Method 4: Storage I/O Pressure
# 1. Create I/O pressure on the host system
dd if=/dev/zero of=/tmp/io-load bs=1M count=1000 &
sync
# 2. Run Claude CLI in loop mode
docker compose run --rm huginn --loop 1
# 3. Monitor for corruption
while true; do
if ! python3 -c "import json; json.load(open('/home/user/.claude.json'))" 2>/dev/null; then
echo "CORRUPTION DETECTED!"
cat ~/.claude.json
break
fi
sleep 5
done
Verification of Corruption
When successfully reproduced, you should see:
# 1. Invalid JSON in the config file
cat ~/.claude.json
# Output shows unterminated string, like:
# {"key": "value", "another": "incomplet
# 2. Claude CLI shows interactive error prompt
docker compose run --rm huginn
# Output shows the "Configuration Error" dialog box
# 3. Backup file exists
ls -la ~/.claude.json.backup.*
# Shows timestamped backup files
Expected vs Actual Behavior
Expected:
- ~/.claude.json should always contain valid JSON
- Claude CLI should handle write failures gracefully
- No interactive prompts in non-interactive environments
Actual:
- ~/.claude.json becomes corrupted with unterminated strings
- Claude CLI shows interactive prompt requiring user input
- Process hangs waiting for input that cannot be provided in containerized environments
System Information
# Collect this information when filing the bug report
docker --version
uname -a
df -h ~/.claude.json # Check filesystem type
mount | grep "\.claude" # Check mount options
Debug Information to Collect
If you successfully reproduce the issue:
# 1. Save the corrupted file
cp ~/.claude.json ~/claude-corrupted-$(date +%s).json
# 2. Check file size and last modification time
ls -la ~/.claude.json
# 3. Check for partial writes (file size changes)
watch -n 0.1 'ls -la ~/.claude.json'
# 4. Check Docker logs for any errors
docker compose logs huginn | grep -i error
# 5. Examine backup files
ls -la ~/.claude.json.backup*
cat ~/.claude.json.backup.* | head -20
Factors That Increase Likelihood
- ✅ Running in Docker container with volume mounts
- ✅ High-frequency Claude CLI invocations (< 5 minute intervals)
- ✅ Abrupt container termination (SIGKILL, host reboot)
- ✅ Multiple concurrent Claude CLI instances
- ✅ Network file systems (NFS, CIFS) for Docker volumes
- ✅ Running on systems with high I/O load
Notes
- Corruption is intermittent - may take multiple attempts
- Most common error: "JSON Parse error: Unterminated string"
- Less common: "Unexpected token" or "Unexpected end of JSON input"
- Backup files (.claude.json.backup.*) may also be corrupted if crash occurs during backup operation
Claude Model
Opus
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.20 (Claude Code)
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
iTerm2
Additional Information
_No response_
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗