Skip to content

[spark-compete] fix(cli): name provider/model + recovery commands in 5 doctor-LLM empty-response errors#735

Open
4gjnbzb4zf-sudo wants to merge 2 commits into
vibeforge1111:masterfrom
4gjnbzb4zf-sudo:sentinel/actionable-error/doctor-llm-empty-response-listing
Open

[spark-compete] fix(cli): name provider/model + recovery commands in 5 doctor-LLM empty-response errors#735
4gjnbzb4zf-sudo wants to merge 2 commits into
vibeforge1111:masterfrom
4gjnbzb4zf-sudo:sentinel/actionable-error/doctor-llm-empty-response-listing

Conversation

@4gjnbzb4zf-sudo

@4gjnbzb4zf-sudo 4gjnbzb4zf-sudo commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

{
"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 llm flow: openai_compatible_chat_completion fires LLM provider returned no choices. (L10462) or LLM provider returned an empty doctor response. (L10466); ollama_chat_completion fires Ollama returned an empty doctor response. (L10492); codex_cli_completion fires Codex CLI returned an empty response. (L10536); claude_cli_completion fires Claude 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) and spark 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> and spark 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.txt to save the redacted prompt for offline review, or spark providers status to verify Ollama is reachable.'",
"Pure-hit path unchanged: any non-empty content still routes through the existing return str(content) / return output paths 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 to spark doctor llm --prompt-out and spark providers status as 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> plus spark providers status as the two recovery commands. The provider/model values are already in scope via the target dict 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-out flag 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 all returned 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 target dict. No new module-level constant, no new import, no behavior change on the network or CLI subprocess paths.",
"review_state_requested": "pr_review"
}
}

@4gjnbzb4zf-sudo

Copy link
Copy Markdown
Contributor Author

QA write-up — for reviewer eyes

TL;DR. Five empty-response SystemExit branches in src/spark_cli/cli.py (the spark doctor llm flow) used to print opaque strings like LLM provider returned no choices. They now name the provider/model that produced the empty payload and point to spark doctor llm --prompt-out <path> and spark providers status so the operator can recover without leaving the shell. The success path is byte-identical -- the new strings only fire when SystemExit was already going to fire.

Before

choices = 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.")
$ spark doctor llm
LLM provider returned no choices.
$

The operator has no way to tell which provider/model just produced the empty payload or what to try next.

After

choices = 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."
    )
$ spark doctor llm
LLM provider returned no choices (provider=openai, model=gpt-4o-mini). 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 --prompt-out flag is the already-documented recovery path that cmd_doctor_llm at L10659 advertises ("Wrote redacted Spark Doctor prompt: ..."). The five empty-response branches now make it visible at the point of failure.

Smoke

$ PYTHONPATH=src python3 -m unittest tests.test_cli.SparkCliTests.test_openai_compatible_empty_response_messages_name_provider_model_and_recovery
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

The test mocks urllib.request.urlopen to return three empty-payload shapes:

  1. choices=[] (OpenAI-compatible no-choices branch);
  2. choices=[{"message": {"content": ""}}] (OpenAI-compatible empty-content branch);
  3. {"message": {"content": ""}} (Ollama empty-content branch).

Each case asserts the SystemExit message names provider/model (or model+base_url for Ollama) and both recovery commands.

Scope

  • 1 file changed for the fix: src/spark_cli/cli.py (35 changed lines across 5 SystemExit branches in openai_compatible_chat_completion, ollama_chat_completion, codex_cli_completion, claude_cli_completion).
  • 1 test file: tests/test_cli.py (63 insertions, 1 new regression test covering 3 cases).
  • git diff --stat origin/master -> 2 files, 93 insertions, 5 deletions.
  • Success path (return str(content) / return output) is byte-identical: the new strings only fire when SystemExit was already going to fire.

Disjoint with sibling PRs

gh pr list --repo vibeforge1111/spark-cli --search "doctor llm provider OR empty doctor response" --state all returns a cluster of doctor-LLM PRs that all address a different concern: HTTPError/URLError on the wire (#207, #309, #415, #552), partial-report save on probe failure (#241), User-Agent headers (#492, #553), provider API key fallback (#487). None touch the five empty-response SystemExit strings. The actionable-error pattern itself was applied to other surfaces by #511 (Unknown installed module), #516 (Unknown bundle), #518 (setup-wizard Unknown X), and #529 (Unknown providers/recommend command). This entry extends the same pattern to the doctor-LLM empty-response branches that no other PR touches.

@4gjnbzb4zf-sudo

Copy link
Copy Markdown
Contributor Author

TL;DR

Four '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 spark doctor llm --prompt-out <path> and spark providers status.

What changes

Files touched: src/spark_cli/cli.py, tests/test_cli.py.

Why this matters

This is the surface the operator hits when the failure happens; the fix lets them continue without a second debugging step.

Reproduction (operator-side)

  1. gh pr checkout
  2. 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).
  3. 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.txt to save the redacted prompt for offline review, or spark providers status to verify Ollama is reachable.'
  4. Pure-hit path unchanged: any non-empty content still routes through the existing return str(content) / return output paths without ever reaching the empty-response branch.

Verification

Review src/spark_cli/cli.py for the targeted change. Run the reproduction; expected outcome: 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> and spark providers status.

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>
@4gjnbzb4zf-sudo 4gjnbzb4zf-sudo force-pushed the sentinel/actionable-error/doctor-llm-empty-response-listing branch from 1b8e8f5 to 921152d Compare June 7, 2026 20:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant