Live demo — data resets daily at 03:00 UTC. Nothing you enter is saved. Server UI →

Accordion Maestro

YAML-driven test orchestration for hardware validation

Station Tools

Central Services

APIs

AI Assistant (MCP)

Connect your AI assistant to this station

Select your AI client below for setup instructions.

Run this command once in your terminal to register this station with Claude CLI:

mcp add --transport http maestro-demo-station http://localhost:7004/mcp
Station: demo-station  •  MCP endpoint: http://localhost:7004/mcp
Accordion Maestro 1.22.0

What's new

1.50.2 2026-08-09
  • ### The AI chat drawer stops flooding station logs
  • A station leaves `Chat__GatewayUrl` empty — AI chat is off for every real user there. The UI
  • substituted `http://localhost:7005` anyway and the drawer probed it on every page load. The probe is
  • caught and the drawer correctly hides itself, but `HttpClientFactory`'s logging handlers write the
  • connection failure, with a `SocketException` stack, before application code ever sees it.
  • Measured on a live station container: **64,702 log lines in 24 hours**, all of it `Connection refused
  • (localhost:7005)`. That is the log an engineer reads when something is actually wrong.
  • An unconfigured gateway now registers a client that makes no request at all. Inventing a
  • plausible-looking default for an absent value was the mistake; a request that should never be made is
  • better not made than made and swallowed. Routine probe logging for the chat client is also filtered to
  • Warning, which removed a further ~1,270 lines a day on the central Dashboard, where the gateway *is*
  • configured and the probes were succeeding.
  • ### `/api/health/detailed` no longer reports "healthy" while returning 503
  • `status` was the literal string `"healthy"` in every response, including the 503. Anything reading the
  • body rather than the status code — a tile, a tool, a person — was told the station was fine while its
  • database was unreachable.
  • ### `get_system_health` returns the diagnosis instead of throwing it away
  • The MCP tool called `GetFromJsonAsync`, which throws on a non-success status, so a 503 surfaced as
  • "Response status code does not indicate success" and the body — naming the failed check and why — was
  • discarded. A station could not resolve its database host and the operator's assistant reported a
  • crashed tool instead of "database unreachable". A diagnostic that only works when there is nothing to
  • diagnose is not a diagnostic.
1.50.1 2026-08-07
  • ### `/api/system/mcp-info` no longer 500s when a config value is blank
  • The endpoint that tells a client where to point its AI assistant threw
  • `UriFormatException: Invalid URI: The URI is empty` and returned a 500. The fallback read
  • `_configuration["PublicApiUrl"] ?? "http://localhost:7000"`, and `??` only catches null — a generated
  • `.env` writes an unset value as `PUBLICAPIURL=`, which reads back as an empty string, sails past the
  • `??`, and reaches `new Uri("")`.
  • Absent and deliberately-empty are different states and only one of them has a default. Now checked
  • with `IsNullOrWhiteSpace` plus `Uri.TryCreate`, which also covers a value that is set but is not a
  • valid absolute URL — something no amount of null-checking would have caught.
  • Seen on a live station on 2026-08-07.
1.50.0 2026-08-04
  • Filling in a start or stop date on Test Results returned nothing.** Any search on `/test-results`
  • with either date box filled came back empty — not "no matching runs", but every run hidden, even when
  • the range obviously covered them. `StartTime` is a Postgres `timestamp with time zone`, and Npgsql
  • refuses to write a `DateTimeOffset` whose `Offset` isn't zero ("only offset 0 (UTC) is supported")
  • rather than converting it. The date pickers tagged the operator's wall-clock time with the *browser's*
  • offset, so from anywhere except UTC every date-filtered query threw before it reached the database.
  • Sweden is UTC+1/+2, so it never worked here; a browser sitting exactly on UTC would have been fine,
  • which is why the failure looked arbitrary. The pickers now normalise to UTC (same instant, offset
  • zero), and both query builders — the Dashboard's and this API's — normalise defensively, so a client
  • passing its own offset on `GET /api/test-results?startTimeFrom=…` can no longer trigger it either.
  • A failed Test Results search no longer masquerades as an empty one.** The page caught the exception,
  • nulled the response and rendered "Perform a search to see results" — indistinguishable from a search
  • that legitimately matched nothing, which is what kept the bug above invisible. Search failures now
  • show the error.
1.49.0 2026-07-29
  • Bug #11 — vendored wheels built for the wrong Python are no longer a silent, misleading failure.**
  • Reported from thea-esharp: a package that correctly vendored its wheels per the SDK docs failed every
  • run with `Could not find a version that satisfies the requirement matplotlib (from versions: none)`,
  • and reinstalling the package — the obvious first move — did not help. The python-runner image ships
  • `python:3.13-slim`, but `docs/sdk/python-dependencies.md` and `scripts/vendor-wheels.ps1` both told
  • authors to download for Python 3.11. pip ignores a compiled wheel whose ABI tag does not match the
  • interpreter and then reports it as *absent*, so a `cp311` wheel on a `cp313` runner is
  • indistinguishable from a wheel that was never vendored. Pure-Python wheels are unaffected, which is
  • why only the compiled dependency was named in the error. Three changes:
  • The docs and `vendor-wheels.ps1` now target Python 3.13, matching the shipping image.
  • `vendor-wheels.ps1` takes a `-PythonVersion` parameter and warns after download about any wheel
  • whose ABI tag does not match the target, so the mismatch is caught at authoring time.
  • The runner now diagnoses the case at install time: on an offline install failure it inspects the
  • ABI tags in `wheels/` and, when they target another interpreter, appends an explicit explanation
  • (running version, the foreign tags found, and the `pip download` line that fixes it) to the error
  • the step returns — replacing the misleading "from versions: none" as the operator-facing message.
  • Documented that `runner_type: python3.11` in test YAML is a routing token that selects the Python
  • runner, not an interpreter pin. It is unrelated to which wheels to vendor.
  • `AllowOnlinePip` was unreachable for any package that vendors a wheel.** The installer branched
  • `if has_wheels → offline / elif allow_online → PyPI`, so once a `wheels/` directory held any `.whl`
  • the online path could never run, and `test_executor`'s "retrying with allow_online=True" retry just
  • ran the same failing offline install again. The two are now a fallback chain, as the docs and the
  • step's own error text always claimed: a failed offline install falls through to PyPI when
  • `AllowOnlinePip` is set. Production is unchanged — `AllowOnlinePip` is false by default and startup
  • always runs offline.
1.46.0 2026-07-27
  • Bug #3 — a measurement on a step whose method threw is now recorded FAIL, never a false PASS.**
  • When a runner method fails (throws → `Success=false`/`ErrorMessage`), any measurement bound to that
  • step is untrustworthy — its value is fabricated or a stale carry-over from a previous step/iteration
  • (e.g. a per-channel sub-sequence where CON2's read threw and the variable still held CON1's value).
  • Both the YAML-measurement path (`ExecuteStepLogicAsync`) and the runner-returned path
  • (`MapResponseToResultAsync`) now hard-fail every measurement on an execution-failed step, so a
  • plausible in-limit reading can no longer be persisted as PASS and corrupt the report / Cp/Cpk.
  • (Residual, not addressed here: a stale value consumed by a *downstream* step that does not itself
  • fail still needs per-run variable-write provenance — tracked separately.)
  • Bug #6 — interactive break-on-fail now parks BEFORE a step's terminate-on-fail action.** The
  • terminate action previously set `isAborting` first and the park was then skipped, so the exact step
  • you most wanted to inspect ran straight to teardown. Break-on-fail now parks first (top-level phase
  • loop and nested sub-sequences), then the terminate action proceeds on resume.
  • Bug #5 — `GET /api/test-results/{id}/steps` accepts a `verdict` filter.** Pass `verdict=FAIL`
  • (etc.) to get only matching steps server-side, so triaging a large run no longer requires pulling
  • and scanning every step. Surfaced via the `get_step_results` MCP tool's new `verdict` parameter.
An unhandled error has occurred. Reload

Rejoining the server...

Rejoin failed... trying again in seconds.

Failed to rejoin.
Please retry or reload the page.

The session has been paused by the server.

Failed to resume the session.
Please retry or reload the page.