Skip to content

bug(slm-frontend): update-all per-stage stage.message is never rendered — operators see that a run was partial, not why #13156

Closed

Description

@mrveiss

Problem

autobot-slm-backend/api/code_sync.py writes a per-stage human-readable summary into stage.message — including the explanation for why a fleet stage was skipped or partially applied — but CodeSyncView.vue never renders it anywhere.

Found while deriving UpdateAllStage/UpdateAllJob from the generated OpenAPI contract (#13138, PR #13152 / 66279b115).

Why it matters

This compounds the defect fixed in #13152. UpdateAllJob.status omitted 'partial', so a partially-successful update-all run matched neither the failure banner (CodeSyncView.vue:913, tests 'failed') nor the success banner (:921, tests 'completed' | 'already_current') and ended with no outcome shown at all. That is now fixed — but the backend's own explanation of what was partial still has no route to the screen, because stage.message is never displayed.

So an operator can now see that a run was partial without seeing why, when the server already computed and sent the reason.

Suggested fix

Render stage.message in the per-stage list, at minimum for non-completed stages. skipped_fleet_nodes (code_sync.py:4270) is now present in the derived type and gives the count to pair with it.

Not filed as part of #13152 because it is a rendering gap rather than a contract mismatch — the type is now correct; the template simply does not use the field.

Activity

  1. mrveiss commented on Jul 31, 2026

    @mrveiss
    OwnerAuthor

    Closed by PR #13167, merged to Dev_new_gui as 1adb1dc06 (closes #13156 and #13157 together — same surface, same underlying defect).

    Both premises confirmed, and #13157 was worse than filed

    stage.message was genuinely never rendered: all ten message references in CodeSyncView.vue are result.message from mutation responses (:285,:297,:333,:360,:386,:415,:416,:429,:570,:573). The stage track (:848-895) showed name, status, sha, deps badge and a log toggle — never the message.

    getJobStatus had zero references outside its own definition (useCodeSync.ts:558) and export (:870) — not even a test, so "tests only" understated it.

    Root cause: handleSyncSelected / handleSyncAll call syncFleet, receive a job_id for an asynchronous rollout, discard it, and show a green "Successfully synced N nodes" toast. A job that failed minutes later reported nothing at all.

    My own filed guidance was wrong, and following it would have preserved the bug

    #13156 said to render the message "at minimum for non-completed stages." That is incorrect. skipped_fleet_nodes (code_sync.py:4879) is folded into the fleet-stage summary at :4932-4938 while that stage is set to success — only the job becomes partial (:4940). Filtering to non-completed stages would have hidden exactly the message this issue was filed about. The message now renders for every stage that carries one, with a test pinning it on a success stage specifically.

    Delivered

    stage.message under each stage box (data-testid="stage-message"). A new Fleet Sync Jobs panel: the queued job_id is polled via getJobStatus (2s, until terminal) showing progress, failed count, failure_reason and per-node message; opening Advanced or hitting refresh calls getRecentJobs for the last 5 jobs with their reasons. Timer cleared in onUnmounted. Nothing deleted. Seven codeSyncView.* keys added to en.json (single-locale app).

    In-scope backend fix: _list_jobs_from_db (autobot-slm-backend/api/code_sync.py:487) omitted failure_reason while _load_job_status_from_db (:470) set it — so every recent job would have rendered a null reason even after the frontend was wired. One line, no schema change.

    Verification — including proof the tests are not vacuous

    type-check clean both sides. lint 16 problems / 0 errors, identical pre-existing set. vitest 219 → 230, identical (empty) failing set.

    Non-vacuity: cp-swapping the pre-fix CodeSyncView.vue back in produced 8 failed / 7 passed in that file — the single new test that still passes is the negative case. Restored byte-identically afterwards.

    Assertions read the DOM, not call counts: [data-testid="stage-message"] equals Updated 2/3 nodes (1 skipped — not operational) for a partial job whose fleet stage is success; [data-testid="fleet-job-failure-reason"] contains the ansible failure after clicking Sync All and advancing the poll timer; a polling test drives running → completed, asserts the text changes 1 / 2 nodes → 2 / 2 nodes, then that polling stops.

    No AutoBot application code was executed (#13090) — frontend tooling only.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions