Skip to content

Thirteen of sixteen mutation handlers report success for a reconcile that failed #709

Description

@rainmanjam

From the poka-yoke audit of 2026-09-04. Lens: fixed-value. Today: rung 0. Device reaches: rung 2 (Warning). Silent.

The mistake available

Write a new mutation handler by copying the nearest neighbour. Both spellings
exist in the same package, and nothing in the signature says which is right.

Counted in non-test code: three return 500, twelve log and return success.

// handlers.go:1641 -- loud
if err := s.reconcile(); err != nil {
    writeError(w, 500, "settings saved but reconcile failed: "+err.Error()); return
}

// handlers.go:2206 -- silent, same file
if err := s.reconcile(); err != nil { s.log.Warn("reconcile after destination update", "err", err) }
writeJSON(w, http.StatusOK, resp)

Silent sites: expert.go:874,909 · handlers.go:2148,2206,2270 ·
renditions.go:120,215,245 · sources.go:472,509,528 ·
oauth_handlers.go:527 · scheduler/runner.go:244.

Why it does not self-correct

Engine.Reconcile returns early on a reconcileOutputs() error, so preview,
clips, captions and loudness are skipped too. Manager.Reconcile returns
firstErr and drops it. Reconcile is event-driven with no ticker — the
engine's own comment at engine.go:3160 says so — so the failure is never
retried. Stored state and the running FFmpeg diverge until the next successful
mutation or a restart.

The sharpest instance

internal/api/expert.go:874-892, the live command-line override:

  1. the reconcile error is swallowed to a Warn
  2. a comment then claims "the command shown back is the one the reconcile above
    just started"
    — false on exactly that branch
  3. the response ships Applied: true as a hardcoded literal

The UI closes the loop: DestinationDialog.tsx:1032toast.success,
MonitoringPage.tsx:210"Saved." and renders the new command back.

The worst case is invisible: handleDeleteDestination returns
{"status":"deleted"}, the row leaves the list, and the FFmpeg child keeps
publishing to a destination the console no longer draws.

Today

Rung 0 — a slog.Warn in a process log. Nothing in the product reads logs.

Device → rung 2, Warning

The pattern already exists in the same file: applyDestinationEnabled
(handlers.go:2322) returns destControl{ReconcileErr: err}.

  1. Carry the reconcile error in the response. handlers.go:2151 and :2210
    already build a warnings array, and engine.ReloadReport is already
    surfaced on handlers.go:1711, so the transport exists.
  2. Collapse the choice into one helper so there is no second spelling to copy.

What stops Control: refusing the write would be wrong. The row genuinely was
saved, and a 500 invites a retry that re-POSTs a destination. The honest ceiling
is that the response states what did not happen.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions