[spark-compete] fix(cli): name provider/model + recovery commands in 5 doctor-LLM empty-response errors#735
Conversation
QA write-up — for reviewer eyesTL;DR. Five empty-response SystemExit branches in Beforechoices = payload.get("choices")
if not choices:
raise SystemExit("LLM provider returned no choices.")
message = choices[0].get("message", {}) if isinstance(choices[0], dict) else {}
content = message.get("content")
if not content:
raise SystemExit("LLM provider returned an empty doctor response.")The operator has no way to tell which provider/model just produced the empty payload or what to try next. Afterchoices = payload.get("choices")
provider_id = str(target.get("provider") or "openai-compatible")
model_id = str(target.get("model") or "default")
if not choices:
raise SystemExit(
f"LLM provider returned no choices (provider={provider_id}, model={model_id}). "
"Try `spark doctor llm --prompt-out spark-doctor-prompt.txt` to save the redacted prompt for offline review, "
"or `spark providers status` to verify the configured provider."
)The SmokeThe test mocks
Each case asserts the SystemExit message names provider/model (or model+base_url for Ollama) and both recovery commands. Scope
Disjoint with sibling PRs
|
…5 doctor-LLM empty-response errors
386a167 to
24faaad
Compare
TL;DRFour 'empty doctor response' SystemExit messages omit the provider/model that just failed and the next-step After the fix: Each of the four empty-response SystemExit messages names the provider+model (or model+base_url for Ollama, or model+CLI path for Codex/Claude) and points to What changesFiles touched: Why this mattersThis is the surface the operator hits when the failure happens; the fix lets them continue without a second debugging step. Reproduction (operator-side)
VerificationReview |
Brings registry.json modules.*.commit up to current remote HEAD for the 7 blessed downstream modules. Clears the test-and-audit "registry pin lags or diverges from remote HEAD" failure on this PR. Same mechanical refresh shape filed as a clean infra PR (vibeforge1111#1391) for repo-wide use. Co-Authored-By: ValhallaBuilder <286693580+4gjnbzb4zf-sudo@users.noreply.github.com>
1b8e8f5 to
921152d
Compare
{
"schema": "spark-compete-hotfix-v1",
"event": "spark-compete-first-event",
"submission_mode": "public_repo_pr",
"submission_target_url": "#735",
"team": {
"name": "SparkThisUp",
"members": ["ValHallaBuilder", "Baz707", "DanFireDash"],
"github_accounts": ["4gjnbzb4zf-sudo"],
"llm_device_holder": "ValHallaBuilder",
"device_holder_github": "4gjnbzb4zf-sudo"
},
"target_repo": {
"id": "vibeforge1111/spark-cli",
"source": "https://github.com/vibeforge1111/spark-cli",
"owner_surface": "spark-cli"
},
"issue": {
"type": "usage_friction",
"severity": "low",
"title": "Four 'empty doctor response' SystemExit messages omit the provider/model that just failed and the next-step",
"actual_behavior": "src/spark_cli/cli.py raises four near-identical SystemExit strings when an LLM call returns an empty response in the
spark doctor llmflow: openai_compatible_chat_completion firesLLM provider returned no choices.(L10462) orLLM provider returned an empty doctor response.(L10466); ollama_chat_completion firesOllama returned an empty doctor response.(L10492); codex_cli_completion firesCodex CLI returned an empty response.(L10536); claude_cli_completion firesClaude CLI returned an empty response.(L10576). None of the four messages names which provider/model/CLI just produced the empty payload, and none names the two recovery commands the operator has at hand:spark doctor llm --prompt-out <path>(already documented at L10659 -- writes the redacted prompt for offline review) andspark providers status(verifies the configured provider). An operator hitting this branch can't tell whether to switch models, re-sign in to the CLI, or fall back to --prompt-out.","expected_behavior": "Each of the four empty-response SystemExit messages names the provider+model (or model+base_url for Ollama, or model+CLI path for Codex/Claude) and points to
spark doctor llm --prompt-out <path>andspark providers status. Non-empty responses (the success path) are byte-identical -- the new strings only fire when SystemExit was already going to fire.","repro_steps": [
"gh pr checkout ",
"PYTHONPATH=src python -m unittest tests.test_cli.SparkCliTests.test_openai_compatible_empty_response_messages_name_provider_model_and_recovery -> ok (three cases: choices=[] for openai-compatible, empty content for openai-compatible, empty content for Ollama; each asserts the new SystemExit message names provider/model/base_url and both recovery commands).",
"Manual: PYTHONPATH=src python -c "from spark_cli.cli import ollama_chat_completion; from unittest.mock import patch; import json; class R:\n def enter(self): return self\n def exit(self,*a): return False\n def read(self): return json.dumps({'message':{'content':''}}).encode()\nwith patch('spark_cli.cli.urllib.request.urlopen', return_value=R()):\n try: ollama_chat_completion({'provider':'ollama','model':'llama3','base_url':'http://localhost:11434'}, 'p')\n except SystemExit as e: print(e)" -> 'Ollama returned an empty doctor response (model=llama3, base_url=http://localhost:11434). Try
spark doctor llm --prompt-out spark-doctor-prompt.txtto save the redacted prompt for offline review, orspark providers statusto verify Ollama is reachable.'","Pure-hit path unchanged: any non-empty content still routes through the existing
return str(content)/return outputpaths without ever reaching the empty-response branch."],
"affected_workflow": "spark doctor llm -- when an LLM call returns an empty payload, the actionable-error message now names which provider/model just failed and which two commands the operator has at hand to recover (save the redacted prompt locally, verify the configured provider) without leaving the shell."
},
"evidence": {
"safe_links_only": true,
"before_after_proof": "Four SystemExit message strings in src/spark_cli/cli.py (L10462, L10466, L10492, L10536, L10576 -- L10462 and L10466 are paired in openai_compatible_chat_completion). Before each: a one-line opaque string (e.g.
LLM provider returned no choices.). After each: the string names the provider/model that just produced the empty payload (provider=, model=, plus base_url for Ollama and cli_path for Codex/Claude), and points tospark doctor llm --prompt-outandspark providers statusas recovery commands. Diff: 1 file modified for the fix (35 changed lines in src/spark_cli/cli.py), 1 test file (63 insertions in tests/test_cli.py for the regression test). The success path (return str(content)/return output) is byte-identical.","links": ["https://github.com//pull/735"],
"forbidden": ["pdf","zip","exe","unknown downloads","shortened links","archives","binaries","tokens","browser cookies","wallet material","raw logs","raw conversations","raw memory","raw patches","private repo maps","private scoring details"]
},
"proposed_fix": {
"approach": "Extend each of the five empty-response SystemExit strings in src/spark_cli/cli.py (openai_compatible_chat_completion: two sites at L10462 and L10466; ollama_chat_completion at L10492; codex_cli_completion at L10536; claude_cli_completion at L10576) to name the provider+model that just failed (or model+base_url for Ollama, or model+CLI path for Codex/Claude) and point to
spark doctor llm --prompt-out <path>plusspark providers statusas the two recovery commands. The provider/model values are already in scope via thetargetdict at each site. No new helper, no new module-level constant, no behavior change on the success path. The recovery-command suggestions match the documented--prompt-outflag the wizard at cmd_doctor_llm (L10659) already supports.","files_expected": ["src/spark_cli/cli.py", "tests/test_cli.py"],
"tests_or_smoke": "PYTHONPATH=src python -m unittest tests.test_cli.SparkCliTests.test_openai_compatible_empty_response_messages_name_provider_model_and_recovery -> ok. The new test mocks urllib.request.urlopen to return three empty-payload shapes (choices=[]; choices=[{message:{content:''}}]; Ollama-shaped empty content) and asserts each SystemExit message names the provider/model/base_url and both recovery commands. The Codex/Claude CLI paths use the same pattern (model + cli_path inline) and follow the same recovery-command shape; their SystemExit branches are exercised by inspection of the patch alongside the openai/ollama tests."
},
"pr": {
"branch": "sentinel/actionable-error/doctor-llm-empty-response-listing",
"title_prefix": "[spark-compete]",
"author_github": "4gjnbzb4zf-sudo",
"body_must_include": ["packet","team","pr_author","repo","actual_behavior","expected_behavior","repro_steps","before_after_proof","tests_or_smoke","duplicate_notes","risk_notes","review_claim"],
"url": "#735"
},
"review_claim": {
"impact_claim": "low",
"evidence_types": ["redacted_terminal_excerpt"],
"duplicate_notes": "Pre-flight
gh pr list --repo vibeforge1111/spark-cli --search 'doctor llm provider OR empty doctor response' --state allreturned doctor-LLM-related PRs that are scope-disjoint from this one. PR #207, #309, #415, #552 all handle HTTPError/URLError on the wire (network surface, not the empty-response message text). PR #241 saves a partial report when the LLM probe fails. PR #553 adds a User-Agent header to doctor requests. PR #492 sends User-Agent on OpenAI-compatible HTTP calls. PR #487 falls back to os.environ for the provider API key. None of those PRs touch the five empty-response SystemExit message strings. Sibling actionable-error PRs in this series have widened the equivalent message on other surfaces (PR #511 Unknown installed module, #516 Unknown bundle, #518 setup-wizard, #529 providers/recommend). This entry extends the same pattern to the doctor-LLM empty-response branches that no other PR touches.","risk_notes": "Local scope: one file modified for the fix, one regression test. The success path (return str(content) / return output) is byte-identical -- the new strings only fire when SystemExit was already going to fire. The provider/model values are already in scope at each site via the
targetdict. No new module-level constant, no new import, no behavior change on the network or CLI subprocess paths.","review_state_requested": "pr_review"
}
}