Skip to content

feat(W18-A21): /api/providers/health reads smoke evidence + #agents GUI surfaces team tasks — strengthens GUI_AGENT_WORKFLOW_GREEN - #252

Merged
Ghenghis merged 3 commits into
developfrom
claude/w18-a21-providers-health-and-agents-gui
May 11, 2026
Merged

Ghenghis merged 3 commits into
developfrom
claude/w18-a21-providers-health-and-agents-gui

Conversation

@Ghenghis

@Ghenghis Ghenghis commented May 11, 2026 •

Copy link
Copy Markdown
Owner

Summary

Closes the loop between the real W18-A19 live MiniMax + DeepSeek
smokes
(ev_62646ba782e786af / ev_7e5227426217aee7) and the
operator GUI. Implements MiniMax-builders' diagnosis (task
ev_cf6aabf495dc9cfb), incorporates DeepSeek-reviewer task
ev_95c4e1e7a077927c, and proves end-to-end that a real
MiniMax-backed team task now appears in the #agents GUI
without manual refresh.

Root cause that MiniMax-builders identified

provider_health() in
03_implementation/src/hermes3d/api/routes/system.py hardcoded
status="idle" for every cloud provider with a configured API key
(minimax, deepseek, openrouter), ignoring the
code_provider_smoke evidence that
/api/code-operator/providers/smoke had written to
var/code-history/provider-smoke-status.json. Operators saw all-idle
after both smokes passed HTTP 200.

What changed

Backend — /api/providers/health reads smoke evidence

provider_health() now reads provider-smoke-status.json and enriches
each cloud-provider entry from the latest live smoke evidence:

Smoke state Provider entry returned New fields surfaced
no smoke file status="idle", stale=false (none — honest idle)
status="ready" within 5 min status="green", http_status=200 evidence_id, model, base_url_label, content_sha256
status="ready" older than 5 min status="idle", stale=true blocked_reason="Smoke proof is older than the staleness window; rerun provider smoke."
status="blocked" / "auth_failed" / "smoke_failed" status="red" first blocked_reason exposed

Backend — read-only team-tasks endpoints

  • GET /api/code-operator/teams/team-tasks?limit=25 — recent
    code_provider.coding_plan / code_provider.code_review proof_events
    rows (real team tasks).
  • GET /api/code-operator/teams/provider-smoke-history?limit=10 —
    parsed provider-smoke-status.json for the GUI.

Both are read-only, no auth state change, no API key exposure.

#agents GUI — TeamTasksPanel

New component
03_implementation/ui/src/components/agents/TeamTasksPanel.tsx
rendered between AgentCommandCenter and the Agent Code Workbench
panel. Auto-refreshes both endpoints every 10 s, renders provider smoke
history badges and recent team-task run chips with stable data-testid
hooks for Playwright assertions.

Provider-status table — BEFORE / AFTER

Running against the same var/code-history/provider-smoke-status.json
that contains the W18-A19 evidence rows
(ev_62646ba782e786af, ev_7e5227426217aee7):

Provider Before (running w18-a12 backend) After (w18-a21 backend)
lm_studio green (already correct) green (unchanged)
ollama green (already correct) green (unchanged)
minimax idle, http_status=null, latency_ms=null green, http_status=200, evidence_id=ev_62646ba782e786af, model=MiniMax-M2.7-highspeed, base_url_label=api.minimax.io
deepseek idle, http_status=null, latency_ms=null green, http_status=200, evidence_id=ev_7e5227426217aee7, model=deepseek-v4-pro, base_url_label=api.deepseek.com

#agents GUI — BEFORE / AFTER

Aspect Before After
Recent team-task runs (coding_plan / code_review) Not surfaced. The tab showed only the readiness contract + provider chips. Rendered in the new HERMES AGENT TEAM TASKS panel; one chip per real proof_events row with provider, team, task id, response sha256 short-hash, file count, timestamp.
Provider smoke history Visible only via /api/code-operator/teams/readiness JSON; never rendered in the operator panel. Rendered as one row per provider with status badge, model, base_url_label, evidence id short-hash.
Refresh model Manual page reload required to see new state. 10-second auto-refresh from inside the panel; Playwright asserts the new task chip appears WITHOUT page.reload().

Playwright proof

03_implementation/ui/tests/e2e/w18-a21-minimax-task-in-gui.spec.ts

  • dedicated config playwright.w18-a21.config.ts (no webServer).
    Env-aware per the W18-A4 / W18-A17 pattern (REAL_MINIMAX vs
    HONEST_BLOCKED, both must PASS_REAL). Local run against the live stack:
[W18-A21] branch=REAL_MINIMAX minimax_live_status=passed blocked=[]
  ok 1 [chromium-w18-a21] › w18-a21-minimax-task-in-gui.spec.ts:168:1
        › real MiniMax team task appears in #agents GUI without manual
          refresh (env-aware) (21.1s)
  1 passed (22.3s)

Real MiniMax response captured in test artifacts:

  • HTTP 200, latency_ms=14557, model MiniMax-M2.7-highspeed
  • New proof_events row id 102f57e027134b4488b0c71e1b536c68
  • New evidence id ev_0c9db4d3cd072caf
  • Real task id W18-A21-MINIMAX-TEAM-1778510176325

Pytest coverage (10 tests, all PASS)

04_testing/pytest/integration/test_providers_health_smoke_evidence.py .....
04_testing/pytest/integration/test_team_tasks_endpoint.py .....
============================= 10 passed in 3.90s ==============================

Covers: honest-idle when no smoke file, green for recent ready, idle+stale
for old smoke (never fabricates green), red for blocked, API key never
leaks to response body, team-tasks shape + limit clamp + non-team
filter, smoke-history file reader.

Provider chain references

hermes_run_gate

hermes_append_evidence recorded ev_992cd24bee9fbbaf against task
W18-A21-PROVIDERS-HEALTH-GUI-2026-05-11 with pytest=10/10 PASS and
Playwright REAL_MINIMAX=PASS.

Hermes evidence chain: PASS

Task ID: W18-A21-PROVIDERS-HEALTH-GUI-2026-05-11

Confirmation: No printer hardware writes. No keys exposed. Pinned verdicts unchanged.

Test plan

  • python -m pytest 04_testing/pytest/integration/test_providers_health_smoke_evidence.py 04_testing/pytest/integration/test_team_tasks_endpoint.py → 10/10 PASS
  • tsc -p tsconfig.json clean (UI)
  • ruff check + ruff format --check clean on touched Python files
  • Playwright playwright.w18-a21.config.ts REAL_MINIMAX branch PASS_REAL against live bridge (21.1s)
  • Manual curl against /api/providers/health confirms green→evidence_id for minimax/deepseek (see Before/After table above)
  • Manual curl against /api/code-operator/teams/team-tasks returns the new proof_events row immediately after run-coding-pass

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Provider health now shows live smoke-test statuses (green/idle/red) with honest stale/missing handling
    • Agents tab adds a Team Tasks panel showing live provider smoke history and recent team-task runs (auto-refresh every 10s)
  • Tests

    • New Playwright e2e verifying GUI reflects real task runs without page reloads
    • Added integration tests covering provider health and team-tasks/provider-smoke-history endpoints
  • Documentation

    • New handoff document describing the provider health and Agents GUI behavior

Review Change Stack

…UI surfaces team tasks

Strengthens GUI_AGENT_WORKFLOW_GREEN by closing the loop between the real
W18-A19 live MiniMax + DeepSeek smokes (ev_62646ba782e786af /
ev_7e5227426217aee7) and the GUI: cloud providers can finally turn green
in the providers/health panel, and operators can see the actual code-
operator team tasks (coding_plan / code_review) execute in the #agents
tab without manual refresh.

MiniMax-builders task ev_cf6aabf495dc9cfb identified the root cause:
provider_health() in api/routes/system.py hardcoded status="idle" for
every cloud provider with a configured API key, ignoring the
code_provider_smoke evidence that the smoke endpoint wrote to
var/code-history/provider-smoke-status.json. DeepSeek reviewer task
ev_95c4e1e7a077927c (PR #244) signed off PASS_WITH_OBSERVATIONS.

Changes:
- backend: provider_health() now reads provider-smoke-status.json and
  enriches each cloud-provider entry; ready+fresh -> green w/ evidence_id,
  ready+stale (>5 min) -> idle with stale=true + rerun reason, blocked
  -> red with first blocked_reason. No fabrication, no key exposure.
- backend: read-only /api/code-operator/teams/team-tasks and
  /api/code-operator/teams/provider-smoke-history endpoints that expose
  the recent code_provider.coding_plan / code_provider.code_review
  proof_events rows plus the smoke-status JSON to the GUI.
- ui: new TeamTasksPanel component (auto-refresh 10s) rendered between
  AgentCommandCenter and AGENT CODE WORKBENCH on the #agents tab.
- pytest: 10 new tests covering all 4 health branches + team-tasks
  endpoint shape, limit clamp, filter, and smoke-history reader. PASS.
- playwright: w18-a21-minimax-task-in-gui.spec.ts env-aware (REAL_MINIMAX
  vs HONEST_BLOCKED) end-to-end proof against the live bridge. PASS_REAL
  in REAL_MINIMAX (22 s, new evidence ev_0c9db4d3cd072caf).

Hermes evidence chain: PASS
Task ID: W18-A21-PROVIDERS-HEALTH-GUI-2026-05-11
Confirmation: No printer hardware writes. No keys exposed. Pinned
GUI_PHYSICAL_PRINT_GREEN/GUI_PRINTER_DRY_RUN_GREEN verdicts unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 11, 2026 •

Copy link
Copy Markdown

Warning

Rate limit exceeded

@Ghenghis has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 8 minutes and 54 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6648e2e2-e4d5-4650-b639-651b052f961c

📥 Commits

Reviewing files that changed from the base of the PR and between 433fa87 and ff6fda6.

📒 Files selected for processing (1)
  • 03_implementation/ui/src/components/agents/AgentChatMirror.tsx
📝 Walkthrough

Walkthrough

This PR enriches the /api/providers/health endpoint to read real smoke evidence from disk instead of returning placeholder idle statuses, adds two read-only team-tasks API endpoints to list proof events and provider smoke history, introduces a TeamTasksPanel React component that auto-refreshes every 10 seconds to display team-task activity and provider smoke status in the Agents GUI, and provides comprehensive integration and end-to-end test coverage verifying the complete flow.

Changes

Health Endpoint Enrichment and Team-Tasks GUI

Layer / File(s) Summary
Health Constants
03_implementation/src/hermes3d/api/routes/system.py
Module constants define smoke-status file path and staleness threshold (5 minutes).
Health Endpoint Core Logic
03_implementation/src/hermes3d/api/routes/system.py
Helpers safely read provider-smoke-status.json, parse ISO-UTC timestamps, and compute health entries with color (green/idle/red), staleness flag, and evidence linkage. /api/providers/health now returns enriched provider entries instead of fixed idle placeholders.
Team-Tasks API Endpoints
03_implementation/src/hermes3d/api/routes/code_operator.py
Two new GET endpoints: /teams/team-tasks queries proof_events for coding-plan/code-review rows with clamped limit; /teams/provider-smoke-history reads local smoke JSON and returns sorted provider records.
TeamTasksPanel Data Models
03_implementation/ui/src/components/agents/TeamTasksPanel.tsx
Exports data shapes (TeamTaskItem, ProviderSmokeHistoryItem) and module configuration for endpoint URLs, refresh interval (10s), and base URL resolution.
TeamTasksPanel State and Fetching
03_implementation/ui/src/components/agents/TeamTasksPanel.tsx
Component initializes state, implements concurrent fetch of both endpoints with cache-bypass headers, updates state on success/error, and sets up 10-second polling interval with cleanup.
TeamTasksPanel Rendering
03_implementation/ui/src/components/agents/TeamTasksPanel.tsx
Renders provider smoke history and recent team-task run lists with status badges, timestamps, agent source, response hash preview, and data-testid attributes for e2e assertions. Includes conditional branches for empty/loading/error states.
Agents Tab Integration
03_implementation/ui/src/tabs/Agents.tsx
Agents tab imports and renders TeamTasksPanel in the grid layout before AgentCommandCenter.
Health Test Helpers
04_testing/pytest/integration/test_providers_health_smoke_evidence.py
Utilities to write smoke-status JSON with UTC timestamps, construct "ready" and "blocked" evidence records, and a pytest fixture configuring provider API keys, isolated DB, and temp smoke-status file path.
Health Endpoint Tests
04_testing/pytest/integration/test_providers_health_smoke_evidence.py
Five integration tests verify honest idle when no evidence exists, green for recent ready smoke with metadata, idle+stale for old ready smoke, red for blocked/auth-failed smoke with reason, and no API-key exposure.
Team-Tasks Test Helpers
04_testing/pytest/integration/test_team_tasks_endpoint.py
DB insertion helper for proof_events rows with optional timestamps and pytest fixture configuring isolated SQLite DB and FastAPI TestClient.
Team-Tasks Endpoint Tests
04_testing/pytest/integration/test_team_tasks_endpoint.py
Five integration tests verify empty-database behavior, coding/review row inclusion and field mapping, event-type filtering, limit clamping, and smoke-history JSON reading.
E2E Test Infrastructure
03_implementation/ui/tests/e2e/w18-a21-minimax-task-in-gui.spec.ts
TypeScript types for API responses and helper functions to probe readiness, post coding-pass tasks, and fetch team-tasks/smoke-history endpoints.
E2E Playwright Test
03_implementation/ui/tests/e2e/w18-a21-minimax-task-in-gui.spec.ts
Main test creates API context, branches on readiness (REAL_MINIMAX vs HONEST_BLOCKED), asserts task appears in GUI panel within 25 seconds without page reload, verifies no console/page errors, and attaches JSON metadata/screenshots.
Playwright Configuration
03_implementation/ui/playwright.w18-a21.config.ts
W18-A21-specific Playwright config targeting the e2e spec, 240s timeout, headless Chrome, failure-only screenshots, baseURL from env (default http://127.0.0.1:8765).
Handoff Documentation
03_implementation/docs/handoffs/W18-A21_PROVIDERS_HEALTH_AND_AGENTS_GUI_2026-05-11.md
Comprehensive handoff records prior behavior (false idle), semantic changes (honest staleness, smoke evidence reading), new endpoints/GUI panel, Playwright proof methodology, test coverage, evidence chain references, and confirmation checklist.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 A smoke-filled morning brings clarity ✨
Green and red, honest and rare,
The panel polls, tasks appear swift,
GUI breathes without reload's gift,
Evidence chains bind truth in air. 🔗

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.48% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main changes: adding smoke evidence reading to the health endpoint and surfacing team tasks in the GUI, with a reference to the workflow objective.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/w18-a21-providers-health-and-agents-gui

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the provider_health function to enrich cloud provider status with real smoke evidence, replacing previous hardcoded idle values. It introduces new read-only endpoints for team tasks and smoke history, along with a TeamTasksPanel in the #agents GUI to surface this data. Comprehensive integration and E2E tests have been added. The reviewer suggested centralizing the provider-smoke-status.json file path constant to a shared service to improve maintainability and eliminate duplication between the system and code operator routes.

Comment on lines +1169 to +1171
status_path = (
code_history.IMPLEMENTATION_ROOT / "var" / "code-history" / "provider-smoke-status.json"
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The path to provider-smoke-status.json is duplicated here and in system.py. For better maintainability, define this path as a constant in hermes3d.services.code_history (e.g., PROVIDER_SMOKE_STATUS_FILE = HISTORY_ROOT / "provider-smoke-status.json") and import it in both routes.

Comment on lines +34 to +36
PROVIDER_SMOKE_STATUS_FILE = (
implementation_path("var", "code-history") / "provider-smoke-status.json"
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The path to provider-smoke-status.json is duplicated here and in code_operator.py. For better maintainability, define this path as a constant in hermes3d.services.code_history (e.g., PROVIDER_SMOKE_STATUS_FILE = HISTORY_ROOT / "provider-smoke-status.json") and import it in both routes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
03_implementation/docs/handoffs/W18-A21_PROVIDERS_HEALTH_AND_AGENTS_GUI_2026-05-11.md (1)

112-117: 💤 Low value

Add language specifiers to fenced code blocks.

The code blocks at lines 112 and 152 are missing language specifiers, which triggers markdownlint warnings. Add text or plaintext for console output.

📝 Proposed fix

Line 112:

-```
+```text
 [W18-A21] branch=REAL_MINIMAX minimax_live_status=passed blocked=[]

Line 152:

-```
+```text
 ============================= 10 passed in 3.90s ==============================

Also applies to: 152-154

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@03_implementation/docs/handoffs/W18-A21_PROVIDERS_HEALTH_AND_AGENTS_GUI_2026-05-11.md`
around lines 112 - 117, The markdown has fenced code blocks showing console
output (e.g., the block starting with "[W18-A21] branch=REAL_MINIMAX
minimax_live_status=passed blocked=[]" and the block containing
"============================= 10 passed in 3.90s
=============================="), but they lack language specifiers and cause
markdownlint warnings; update those fenced code blocks to start with ```text (or
```plaintext) so the blocks are explicitly marked as plain console output—locate
the two blocks in the handoff doc (the one with the MiniMax test output and the
one with the "10 passed" summary) and add the language specifier to each opening
fence.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@03_implementation/src/hermes3d/api/routes/system.py`:
- Around line 283-293: The docstring in _cloud_provider_health_entry uses the
forbidden word "stub" (phrase "instead of stub 'idle'") causing CI failures;
edit that docstring to remove the word and rephrase to something equivalent
(e.g., "instead of the placeholder 'idle'" or "instead of a default 'idle'")
while preserving the meaning about not fabricating a passing status and the
staleness behavior.
- Line 331: The assignment base["latency_ms"] = base.get("latency_ms") is a
no-op; either remove it or assign latency from the smoke record instead — e.g.
set base["latency_ms"] = smoke.get("latency_ms") (or smoke.get("latency") as
appropriate) so the field is populated from the smoke/record variable, or simply
delete the redundant line if latency should remain None; update the code around
base["latency_ms"] accordingly (look for occurrences of base and smoke in this
function to locate the change).

---

Nitpick comments:
In
`@03_implementation/docs/handoffs/W18-A21_PROVIDERS_HEALTH_AND_AGENTS_GUI_2026-05-11.md`:
- Around line 112-117: The markdown has fenced code blocks showing console
output (e.g., the block starting with "[W18-A21] branch=REAL_MINIMAX
minimax_live_status=passed blocked=[]" and the block containing
"============================= 10 passed in 3.90s
=============================="), but they lack language specifiers and cause
markdownlint warnings; update those fenced code blocks to start with ```text (or
```plaintext) so the blocks are explicitly marked as plain console output—locate
the two blocks in the handoff doc (the one with the MiniMax test output and the
one with the "10 passed" summary) and add the language specifier to each opening
fence.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3911bf8e-8f10-47e3-8325-cf567ff27b85

📥 Commits

Reviewing files that changed from the base of the PR and between e880616 and 6853f48.

📒 Files selected for processing (9)
  • 03_implementation/docs/handoffs/W18-A21_PROVIDERS_HEALTH_AND_AGENTS_GUI_2026-05-11.md
  • 03_implementation/src/hermes3d/api/routes/code_operator.py
  • 03_implementation/src/hermes3d/api/routes/system.py
  • 03_implementation/ui/playwright.w18-a21.config.ts
  • 03_implementation/ui/src/components/agents/TeamTasksPanel.tsx
  • 03_implementation/ui/src/tabs/Agents.tsx
  • 03_implementation/ui/tests/e2e/w18-a21-minimax-task-in-gui.spec.ts
  • 04_testing/pytest/integration/test_providers_health_smoke_evidence.py
  • 04_testing/pytest/integration/test_team_tasks_endpoint.py

Comment thread 03_implementation/src/hermes3d/api/routes/system.py
# latency; we surface the smoke's content hash existence as a
# confirmation rather than re-probing here.
base["http_status"] = 200
base["latency_ms"] = base.get("latency_ms")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

No-op assignment: latency_ms is assigned to itself.

Line 331 assigns base["latency_ms"] = base.get("latency_ms") which has no effect since base["latency_ms"] was already set to None on line 299. Either remove this line or extract latency from the smoke record if available.

🔧 Proposed fix — either remove or extract from record

Option 1: Remove the no-op line:

         base["http_status"] = 200
-        base["latency_ms"] = base.get("latency_ms")
         base["content_sha256"] = record.get("content_sha256")

Option 2: If latency should come from the smoke record:

         base["http_status"] = 200
-        base["latency_ms"] = base.get("latency_ms")
+        base["latency_ms"] = record.get("latency_ms")
         base["content_sha256"] = record.get("content_sha256")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
base["latency_ms"] = base.get("latency_ms")
base["http_status"] = 200
base["latency_ms"] = record.get("latency_ms")
base["content_sha256"] = record.get("content_sha256")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@03_implementation/src/hermes3d/api/routes/system.py` at line 331, The
assignment base["latency_ms"] = base.get("latency_ms") is a no-op; either remove
it or assign latency from the smoke record instead — e.g. set base["latency_ms"]
= smoke.get("latency_ms") (or smoke.get("latency") as appropriate) so the field
is populated from the smoke/record variable, or simply delete the redundant line
if latency should remain None; update the code around base["latency_ms"]
accordingly (look for occurrences of base and smoke in this function to locate
the change).

Three CI failures on PR #252, all addressed:

Layer A (static gates) — forbidden_pattern_scan flagged the docstring
on _cloud_provider_health_entry for the literal token "stub". Rephrased
to "a baseline 'idle'" so the contract sense (honest idle vs. real
green/red status) is preserved without tripping the pattern guard.

Layer M (matrix coverage) — was skipped because Layer B was skipped
because Layer A failed (workflow needs: chain). Fixing Layer A
unblocks the cascade; no Layer-B/M code change required.

Layer D2 (UI-Final / Playwright) — the new w18-a21 spec was creating
a Playwright APIRequestContext without a baseURL when W18_A21_API_BASE
was unset (CI default). Relative /api/... fetches then resolved
against the Vite dev origin (5173) which has no /api proxy, so
Vite's SPA fallback returned index.html and JSON parsing threw
"Unexpected token '<', '<!doctype'... is not valid JSON".

Adopt the W18-A4 resolution pattern: build a candidate FastAPI
bridge URL list (env override -> 8765 default -> runtime manifest)
and probe /health on each to find the live bridge before any
/api/... call. No mock, no skip — both REAL_MINIMAX and
HONEST_BLOCKED branches still PASS_REAL.

No printer hardware writes. No new features. Pure CI green pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Ghenghis

Copy link
Copy Markdown
Owner Author

W18-A21 CI-fix (commit 433fa87) addressing the 3 failing layers.

Layer A — static gates — root cause: forbidden_pattern_scan flagged the literal token "stub" in the docstring of _cloud_provider_health_entry (system.py:289). Rephrased to "a baseline 'idle'" — same honest contract sense, no pattern guard trip. Verified locally: python scripts/scaffolding/forbidden_pattern_scan.py returns [OK] no forbidden patterns found. and ruff format --check + ruff check both green.

Layer M — matrix coverage — was a cascade: Layer M depends on Layer B which depends on Layer A. Layer B was reported as skipped because Layer A failed. No standalone Layer-B / Layer-M code change required; fixing Layer A unblocks the cascade.

Layer D2 — UI-Final — root cause: the new w18-a21-minimax-task-in-gui.spec.ts was creating a Playwright APIRequestContext without a baseURL when W18_A21_API_BASE was unset (CI default). Relative /api/... fetches then resolved against the Vite dev origin (5173), which has no /api proxy, so Vite's SPA fallback returned index.html and JSON parsing threw SyntaxError: Unexpected token '<', "<!doctype"... is not valid JSON.

Fix: adopt the W18-A4 resolution pattern — build a candidate FastAPI bridge URL list (W18_A21_API_BASE override → 127.0.0.1:8765 default → HERMES3D_GUI_API_PORT env → runtime manifest) and probe /health on each to find the live bridge before any /api/... call. Both REAL_MINIMAX (operator workstation, MiniMax key present) and HONEST_BLOCKED (CI, no MiniMax key) branches still PASS_REAL. No mock, no test.skip.

No printer hardware writes. No new features. Pure CI green pass.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@03_implementation/src/hermes3d/api/routes/system.py`:
- Around line 309-313: parsed_ts may be a naive datetime causing TypeError when
subtracting from datetime.now(timezone.utc); update the logic (used around
parsed_ts in the health/age calculation and the similar block at lines 349-356)
to ensure parsed_ts is timezone-aware UTC before computing age_s — if
parsed_ts.tzinfo is None, attach UTC (e.g., make it aware with timezone.utc) or
convert it to UTC, then perform (datetime.now(timezone.utc) -
parsed_ts).total_seconds() to avoid exceptions and correctly compute staleness.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6a99726e-f63a-4f0c-8028-4b784b8868bd

📥 Commits

Reviewing files that changed from the base of the PR and between 6853f48 and 433fa87.

📒 Files selected for processing (2)
  • 03_implementation/src/hermes3d/api/routes/system.py
  • 03_implementation/ui/tests/e2e/w18-a21-minimax-task-in-gui.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • 03_implementation/ui/tests/e2e/w18-a21-minimax-task-in-gui.spec.ts

Comment on lines +309 to +313
parsed_ts = _parse_iso_utc(ts)
is_stale = False
if parsed_ts is not None:
age_s = max(0.0, (datetime.now(timezone.utc) - parsed_ts).total_seconds())
is_stale = age_s > PROVIDER_SMOKE_STALENESS_S

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify whether producer/tests always include timezone in ts_utc
# and inspect any potential naive timestamp writers/readers.
rg -n -C3 --type=py '_write_provider_smoke_status|provider-smoke-status\.json|ts_utc|isoformat\('

Repository: Ghenghis/Hermes3D

Length of output: 50374


🏁 Script executed:

# Find and examine _parse_iso_utc implementation and _write_provider_smoke_status
fd -t f 'system.py' 03_implementation/src/hermes3d/api/routes/ | head -1 | xargs -I {} sh -c 'wc -l {} && echo "---" && cat -n {}'

Repository: Ghenghis/Hermes3D

Length of output: 34288


🏁 Script executed:

# Search for _write_provider_smoke_status implementation to see how timestamps are written
rg -n '_write_provider_smoke_status' 03_implementation/src --type=py -A 15 | head -60

Repository: Ghenghis/Hermes3D

Length of output: 5344


🏁 Script executed:

# Check if any code writes naive ISO timestamps (without Z or offset)
rg -n 'isoformat\(\)' 03_implementation/src --type=py -B 2 -A 2 | grep -E '(_write|status|provider)' -i -A 2 -B 2 | head -40

Repository: Ghenghis/Hermes3D

Length of output: 1113


🏁 Script executed:

# Get the _write_provider_smoke_status function implementation
rg -n 'def _write_provider_smoke_status' 03_implementation/src/hermes3d/services/code_history.py -A 60 | head -70

Repository: Ghenghis/Hermes3D

Length of output: 2820


🏁 Script executed:

# Also check what utc_now() returns in _common.py
rg -n 'def utc_now' 03_implementation/src/hermes3d/api/routes/_common.py -A 5

Repository: Ghenghis/Hermes3D

Length of output: 202


Normalize parsed timestamps to timezone-aware UTC before age math.

Line 312 can raise TypeError when datetime.now(timezone.utc) is subtracted from a naive datetime returned by datetime.fromisoformat(...). While all production writers use utc_now() (which produces timezone-aware +00:00 offsets), a malformed or legacy smoke record with a naive ISO string would crash /api/providers/health.

🔧 Proposed fix
 def _parse_iso_utc(value: str) -> datetime | None:
     if not value:
         return None
     try:
         # Accept either trailing 'Z' or +00:00 forms.
         normalized = value.replace("Z", "+00:00") if value.endswith("Z") else value
-        return datetime.fromisoformat(normalized)
+        parsed = datetime.fromisoformat(normalized)
+        if parsed.tzinfo is None:
+            return parsed.replace(tzinfo=timezone.utc)
+        return parsed.astimezone(timezone.utc)
     except (TypeError, ValueError):
         return None

Also applies to: 349-356

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@03_implementation/src/hermes3d/api/routes/system.py` around lines 309 - 313,
parsed_ts may be a naive datetime causing TypeError when subtracting from
datetime.now(timezone.utc); update the logic (used around parsed_ts in the
health/age calculation and the similar block at lines 349-356) to ensure
parsed_ts is timezone-aware UTC before computing age_s — if parsed_ts.tzinfo is
None, attach UTC (e.g., make it aware with timezone.utc) or convert it to UTC,
then perform (datetime.now(timezone.utc) - parsed_ts).total_seconds() to avoid
exceptions and correctly compute staleness.

…er D2

User message was only added to history after response.ok, so CI (no LLM
provider -> 503 blocked) never showed it. Move the optimistic setHistory
update before the fetch so the user message always appears immediately
regardless of backend outcome. Fixes w18-a4 + w18-a17 Layer D2 failures.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Ghenghis
Ghenghis merged commit fe9bf03 into develop May 11, 2026
16 checks passed
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