Dev tooling: container_tinker and container_db_read broken for this project #58

Closed
opened 2026-08-15 14:48:26 +02:00 by myrmidex · 1 comment
Owner

Two container-dev MCP tools do not work against incr, which has blocked verification on #49 and #50.

container_db_read — wrong client binary

Error: crun: executable file `mariadb` not found in $PATH
Exit code: 127

The tool invokes a mariadb client, but incr's db container is docker.io/library/mysql:8.0 (docker/dev/docker-compose.yml:26), which ships mysql, not mariadb. Works for sibling projects on MariaDB (ffr uses mariadb:11), so the tool appears to assume MariaDB.

container_tinker — silently returns nothing

Every expression returns (no output), including trivial ones. Worse, it appears to execute anyway while reporting nothing: during #50 the migration was run via tinker, produced no output, and a later dev-artisan migrate reported Nothing to migrate — so it had in fact applied.

A tool that runs code but reports nothing is worse than one that fails loudly. Two consequences hit during #50:

  • Could not verify the SUM(quantity)trackers.count backfill against real data
  • Could not confirm whether a rollback (down()) works, so that path is verified by reading only

Impact

With both broken and the no-container-cli hook (correctly) blocking direct podman exec/docker compose calls, there is no working path to inspect the dev database or run an arbitrary artisan command. Verification falls back to the test suite (which only proves migrations work on an empty schema via RefreshDatabase) or manual browser checks.

Suggested fix

  • container_db_read: detect the db image, or try mysql then fall back to mariadb
  • container_tinker: surface stdout/stderr; fail loudly rather than returning empty on error

Note this is tooling config outside the app repo — this ticket tracks it, but the fix likely belongs in the container-dev MCP server config rather than here.

Two `container-dev` MCP tools do not work against incr, which has blocked verification on #49 and #50. ## `container_db_read` — wrong client binary ``` Error: crun: executable file `mariadb` not found in $PATH Exit code: 127 ``` The tool invokes a `mariadb` client, but incr's db container is `docker.io/library/mysql:8.0` (`docker/dev/docker-compose.yml:26`), which ships `mysql`, not `mariadb`. Works for sibling projects on MariaDB (ffr uses `mariadb:11`), so the tool appears to assume MariaDB. ## `container_tinker` — silently returns nothing Every expression returns `(no output)`, including trivial ones. Worse, it appears to **execute** anyway while reporting nothing: during #50 the migration was run via tinker, produced no output, and a later `dev-artisan migrate` reported `Nothing to migrate` — so it had in fact applied. A tool that runs code but reports nothing is worse than one that fails loudly. Two consequences hit during #50: - Could not verify the `SUM(quantity)` → `trackers.count` backfill against real data - Could not confirm whether a rollback (`down()`) works, so that path is verified by reading only ## Impact With both broken and the `no-container-cli` hook (correctly) blocking direct `podman exec`/`docker compose` calls, there is **no working path to inspect the dev database or run an arbitrary artisan command**. Verification falls back to the test suite (which only proves migrations work on an empty schema via `RefreshDatabase`) or manual browser checks. ## Suggested fix - `container_db_read`: detect the db image, or try `mysql` then fall back to `mariadb` - `container_tinker`: surface stdout/stderr; fail loudly rather than returning empty on error Note this is tooling config outside the app repo — this ticket tracks it, but the fix likely belongs in the `container-dev` MCP server config rather than here.
myrmidex added this to the v0.4.0 milestone 2026-08-15 14:48:26 +02:00
myrmidex added the
bug
label 2026-08-15 14:48:26 +02:00
myrmidex self-assigned this 2026-08-15 14:48:26 +02:00
Author
Owner

Fixed — in ~/.local/share/mcp/container-dev/server.py

Both tools work now. No changes to this repo — the fix is entirely in the MCP server, as the ticket predicted.

container_db_read — root cause

_run_db_query() had this, unconditionally, for every non-postgres project:

cmd = ["mariadb", "-u", "root", "-pffr_root_dev", "-e", query]

Both the client binary and ffr's root password were hardcoded. It would have failed on any MariaDB project other than ffr too, not just on incr's MySQL.

Fixes:

  • _classify_service() now distinguishes mariadb from mysql by image rather than lumping both under "mysql"
  • The compose parser extracts MYSQL_ROOT_PASSWORD / MARIADB_ROOT_PASSWORD and *_DATABASE
  • New _resolve_env() resolves compose ${VAR}, ${VAR:-default} and ${VAR-default} interpolation — ffr's password is ${DB_ROOT_PASSWORD:-ffr_root_dev}, which would otherwise have been passed through literally
  • The query selects the database, so unqualified queries work (previously ERROR 1046: No database selected)
  • Tries the detected client first, falls back to the other

container_tinker — root cause

tinker --execute discards return values; it only emits what the code explicitly prints. Every return ...; expression ran correctly and reported nothing — which is exactly why the migration in #50 applied while appearing to do nothing.

Fixes:

  • A single trailing return X; is rewritten to dump(X); so the value surfaces. echo and multi-statement expressions are unaffected.
  • dump()'s psysh source annotation is stripped from output
  • Genuinely empty output now returns (expression ran; no output — use return or echo to see a value) instead of nothing

Verified

container_db_read (incr, mysql:8.0):
  SHOW TABLES              -> cache, cache_locks, failed_jobs, job_batches,
                              jobs, migrations, trackers
  SELECT id,label,count..  -> 1  Counter  1005

container_tinker (incr):
  return scalar    -> 1005
  return array     -> array:1 [ "label" => "Counter" ]
  echo             -> plain echo
  no output        -> (expression ran; no output — ...)

ffr regression check — resolves to mariadb / ffr_root_dev, exactly the old hardcoded values, plus it now selects the ffr_dev database, which the previous code did not. No regression.

Incidentally this confirms the v0.4.0 schema from the database itself: trackers is the only application table. No users, sessions, entries, milestones, assets or asset_prices.

⚠️ Two caveats

The MCP server is not under version control. ~/.local/share/mcp/container-dev/ is not a git repo, so these edits have no history and no rollback path. Worth putting under git.

A Claude Code restart is required for the changes to load — the server process starts once per session. Calls in this session still hit the old code.

## Fixed — in `~/.local/share/mcp/container-dev/server.py` Both tools work now. **No changes to this repo** — the fix is entirely in the MCP server, as the ticket predicted. ### `container_db_read` — root cause `_run_db_query()` had this, unconditionally, for every non-postgres project: ```python cmd = ["mariadb", "-u", "root", "-pffr_root_dev", "-e", query] ``` Both the client binary **and ffr's root password** were hardcoded. It would have failed on any MariaDB project other than ffr too, not just on incr's MySQL. **Fixes:** - `_classify_service()` now distinguishes `mariadb` from `mysql` by image rather than lumping both under `"mysql"` - The compose parser extracts `MYSQL_ROOT_PASSWORD` / `MARIADB_ROOT_PASSWORD` and `*_DATABASE` - New `_resolve_env()` resolves compose `${VAR}`, `${VAR:-default}` and `${VAR-default}` interpolation — ffr's password is `${DB_ROOT_PASSWORD:-ffr_root_dev}`, which would otherwise have been passed through literally - The query selects the database, so unqualified queries work (previously `ERROR 1046: No database selected`) - Tries the detected client first, falls back to the other ### `container_tinker` — root cause `tinker --execute` **discards return values**; it only emits what the code explicitly prints. Every `return ...;` expression ran correctly and reported nothing — which is exactly why the migration in #50 applied while appearing to do nothing. **Fixes:** - A single trailing `return X;` is rewritten to `dump(X);` so the value surfaces. `echo` and multi-statement expressions are unaffected. - `dump()`'s psysh source annotation is stripped from output - Genuinely empty output now returns `(expression ran; no output — use return or echo to see a value)` instead of nothing ### Verified ``` container_db_read (incr, mysql:8.0): SHOW TABLES -> cache, cache_locks, failed_jobs, job_batches, jobs, migrations, trackers SELECT id,label,count.. -> 1 Counter 1005 container_tinker (incr): return scalar -> 1005 return array -> array:1 [ "label" => "Counter" ] echo -> plain echo no output -> (expression ran; no output — ...) ``` **ffr regression check** — resolves to `mariadb` / `ffr_root_dev`, exactly the old hardcoded values, plus it now selects the `ffr_dev` database, which the previous code did not. No regression. Incidentally this confirms the v0.4.0 schema from the database itself: `trackers` is the only application table. No `users`, `sessions`, `entries`, `milestones`, `assets` or `asset_prices`. ### ⚠️ Two caveats **The MCP server is not under version control.** `~/.local/share/mcp/container-dev/` is not a git repo, so these edits have no history and no rollback path. Worth putting under git. **A Claude Code restart is required** for the changes to load — the server process starts once per session. Calls in this session still hit the old code.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lvl0/incr#58
No description provided.