cptr (installed via uvx cptr@latest run) crashes on startup with an unhandled JSONDecodeError when it auto-detects a locally installed opencode CLI and probes it as an agent profile. This kills the entire FastAPI app during lifespan startup, not just that one profile.
Environment:
- Arch Linux
- opencode installed via pacman/AUR, fully functional and logged in
- cptr run via uvx cptr@latest run
- Python 3.14 (via uv's resolved interpreter) — also reproduced when pinning --python 3.12, so not Python-version-specific
Traceback:
File ".../cptr/app.py", line 67, in lifespan
await warm_model_cache(app.state)
File ".../cptr/routers/chat.py", line 281, in warm_model_cache
await asyncio.gather(*tasks)
File ".../cptr/utils/agents/detection.py", line 690, in get_available_agent_model_entries
status = await get_agent_status(app_state)
File ".../cptr/utils/agents/detection.py", line 648, in get_agent_status
detected = await detect_profile(profile)
File ".../cptr/utils/agents/detection.py", line 267, in detect_profile
models = await _probe_acp_models(command, profile, auth_method_id="oauth-personal")
File ".../cptr/utils/agents/detection.py", line 589, in _probe_acp_models
await client.close()
File ".../cptr/utils/agents/acp.py", line 85, in close
await task
File ".../cptr/utils/agents/acp.py", line 183, in _reader_loop
extracted = _extract_json_message(buffer)
File ".../cptr/utils/agents/acp.py", line 261, in _extract_json_message
return json.loads(line.decode()), buffer[line_end + 1 :]
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
Root cause (confirmed via manual reproduction):
For the OpenCode profile type, cptr spawns:
opencode serve --hostname=127.0.0.1 --port=
Running that exact command by hand shows opencode writes plain human-readable log lines to stdout on startup, not JSON:
Warning: OPENCODE_SERVER_PASSWORD is not set; server is unsecured.
opencode server listening on http://127.0.0.1:47645
cptr's _reader_loop/_extract_json_message appears to treat this subprocess's stdout as a newline-delimited JSON-RPC stream (the ACP convention used for other agent types), and immediately fails json.loads() on the first non-JSON line. This is reproducible even with a no-op stub binary named opencode on PATH that produces zero stdout output before exiting — json.loads("") throws the identical error, confirming this isn't about opencode's specific output but about cptr expecting JSON where serve mode never produces it.
Impact: Any user with opencode installed and discoverable on PATH cannot start cptr at all — a single bad profile probe takes down the entire app during lifespan startup (asyncio.gather(*tasks) in warm_model_cache has no per-task error isolation, and detect_profile/_probe_acp_models don't catch JSONDecodeError).
Suggested fixes:
- Catch and isolate per-profile detection errors in warm_model_cache/get_available_agent_model_entries so one broken agent profile can't crash app startup.
- For the OpenCode profile type specifically, don't reuse the ACP JSON-RPC stdout reader against opencode serve — its stdout is plain log text, not JSON-RPC; communication should happen over HTTP against the bound port instead.
cptr (installed via uvx cptr@latest run) crashes on startup with an unhandled JSONDecodeError when it auto-detects a locally installed opencode CLI and probes it as an agent profile. This kills the entire FastAPI app during lifespan startup, not just that one profile.
Environment:
Traceback:
File ".../cptr/app.py", line 67, in lifespan
await warm_model_cache(app.state)
File ".../cptr/routers/chat.py", line 281, in warm_model_cache
await asyncio.gather(*tasks)
File ".../cptr/utils/agents/detection.py", line 690, in get_available_agent_model_entries
status = await get_agent_status(app_state)
File ".../cptr/utils/agents/detection.py", line 648, in get_agent_status
detected = await detect_profile(profile)
File ".../cptr/utils/agents/detection.py", line 267, in detect_profile
models = await _probe_acp_models(command, profile, auth_method_id="oauth-personal")
File ".../cptr/utils/agents/detection.py", line 589, in _probe_acp_models
await client.close()
File ".../cptr/utils/agents/acp.py", line 85, in close
await task
File ".../cptr/utils/agents/acp.py", line 183, in _reader_loop
extracted = _extract_json_message(buffer)
File ".../cptr/utils/agents/acp.py", line 261, in _extract_json_message
return json.loads(line.decode()), buffer[line_end + 1 :]
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
Root cause (confirmed via manual reproduction):
For the OpenCode profile type, cptr spawns:
opencode serve --hostname=127.0.0.1 --port=
Running that exact command by hand shows opencode writes plain human-readable log lines to stdout on startup, not JSON:
Warning: OPENCODE_SERVER_PASSWORD is not set; server is unsecured.
opencode server listening on http://127.0.0.1:47645
cptr's _reader_loop/_extract_json_message appears to treat this subprocess's stdout as a newline-delimited JSON-RPC stream (the ACP convention used for other agent types), and immediately fails json.loads() on the first non-JSON line. This is reproducible even with a no-op stub binary named opencode on PATH that produces zero stdout output before exiting — json.loads("") throws the identical error, confirming this isn't about opencode's specific output but about cptr expecting JSON where serve mode never produces it.
Impact: Any user with opencode installed and discoverable on PATH cannot start cptr at all — a single bad profile probe takes down the entire app during lifespan startup (asyncio.gather(*tasks) in warm_model_cache has no per-task error isolation, and detect_profile/_probe_acp_models don't catch JSONDecodeError).
Suggested fixes: