# E2E failure remediation — real bugs plus stale-test cleanup

- **Status:** RECOVERY DELEGATED — the 2026-08-15 outbound-SSH blocker is stale. Direct SSH to debian1/2/3 is healthy on 2026-08-19; current dispatcher/runtime-source recovery and completion are owned by `2026-08-19-e2e-remediation-recovery.md`.
- **Owner request (2026-08-15):** "fix all. you fix the real bugs, and have subagents fix all the tests. send parallel subagents for non conflicting tasks", plus "make sure that tests are testing feature correctness, not feature existence", "after fixing the real 2 bugs make sure to push your code", and "we should not need comments plugin for anything btw so this is not just bad setup its complete nonsense".

## Corrected failure census

An earlier count of "30 failures" was wrong: the parsing script treated any `<testcase>` child as a failure, but `<system-out>` is console output. The real count is **16** failing tests. Only `<failure>`/`<error>` children indicate failure in `tests/e2e/test-results/junit.xml`.

| Cluster | Count | Verdict |
|---|---|---|
| `translation-settings.spec.js` — targets a removed Translation tab and non-existent element ids | 7 | stale tests |
| `settings.spec.js` — tab count asserts 4, there are 5 (Exceptions shipped) | 1 | stale test |
| `settings.spec.js` — auto-translate persist + cache-TTL validation | 2 | **real product bug** |
| `journeys/UJ-006`, `UJ-007` — assert a `timeout` setting that does not exist | 4 | stale tests / draft-doc drift |
| `admin-snappy-visual-contract.spec.js` — account route paints a spinner, not a shaped skeleton | 1 | **real product bug** |
| `ui-parity-comments-settings.spec.js` — drives a different plugin's admin page | 1 | deleted (owner: "complete nonsense") |

## Real bug 1 — invalid settings saved anyway (this worktree)

`admin/src/pages/settings.js` `handleSettingChange()` called `debouncedSave()` twice: once inside the `validateSetting()` branch and again unconditionally, so a rejected value saved regardless. Validation was decorative. Two supporting defects in `admin/src/components/FormField.js`: `parseInt` silently truncated `60.5` into a valid `60`, and the handler wrote `state[key]` directly, bypassing the caller's validation.

Fixed: validate first and return on failure; revert the control to the last accepted value (`restoreSettingControl`); `valueAsNumber` instead of `parseInt`; `state` written by the caller when an `onUpdate` exists (callers passing `null` keep the old direct-write path — `languages.js` relies on it).

Build and lint clean. **E2E NOT run** — blocked, see below.

## Real bug 2 — account route loading state (worktree `ipz-account-skeleton`)

`admin/src/pages/account.js` `drawLoading()` rendered `SpinnerWithText`; the shipped contract forbids legacy spinners and requires shaped skeletons whose height matches the final content. Replaced with `SkeletonTable(4, { columns: 2 })`, matching the real 2-column/4-row account table. Build clean; **E2E NOT run** — blocked.

## Test work (all verified where the fleet was still reachable)

- `ipz-test-journeys` — **12/12 green on chromium+firefox.** Removed the phantom `timeout` assertions; replaced with a real `batch_size` boundary test (genuinely validated by the schema); made `updated_fields` order-independent; corrected both journey docs. Root-caused UJ-007's 120s hang: the Reset button only renders inside the Advanced tab, and `page.reload()` resets the SPA to General.
- `ipz-test-remove-parity` — deleted the cross-plugin spec; removed one stale reference in `.claude/skills/verification.md`. Verified collection intact via a remote `--list`.
- `ipz-test-translation-settings` — 7 existence-tests deleted, 4 correctness tests written (tone, auto-publish, debug-mode persistence; tab navigation swapping panel content). **Not verified** — blocked.
- `ipz-test-settings-tabs` — in progress when the blocker hit.

## Blocker

Outbound SSH is killed from the owning session: TCP connects, then the process dies mid-handshake with zero output and exit 1. `ssh -V` works; the fleet itself is healthy (`~/.claude/local-gate.log` shows other sessions dispatching to debian2/debian3 concurrently). Four subagents independently hit the identical signature — `e2e-remote` dies right after its config write and never reaches the gate queue. Not caused by any code change here.

Contributing environment state: workstation load average 19–33 on 2 cores; `~/.claude/run/local-gate/` holds ~17,950 lock/epoch files (99 MB).

## Slot capacity finding

The port-8080 lock is **per machine**, so debian1/2/3 are three independent E2E slots. All three carry the `e2e` role and are `reachable`. Pinning every agent to debian1 serialised them behind one lock; debian2 and debian3 were verified capable (podman present, playwright cache present, warm snapshot, free port) and agents were redistributed.

## Product findings raised, deliberately NOT actioned (owner decision)

- `timeout` is specified in draft journey docs (and the option name `ipz_api_timeout`) but exists nowhere in the product.
- `log_level` and `character_limit` are UI controls with no REST schema entry and no handler — the SPA can submit them and the server returns 200 while persisting nothing. Same phantom-setting shape as `timeout`.
- `ipz_warm_cache` is reset by `resetSettings()` but never returned by `getSettings()`, so journey restore cannot see or restore it.
- `admin-snappy-all-pages.spec.js` has a top-level route-contract assertion that fails on master, which breaks a full-suite `--list`. Pre-existing and unrelated to this work.

## Next executable action

Superseded by `2026-08-19-e2e-remediation-recovery.md`. SSH has recovered; the remaining current blocker is the sanctioned gate dispatcher/runtime-source state. Once that is repaired safely: run `settings.spec.js` (validation fix) and `admin-snappy-visual-contract.spec.js` (account fix) on debian2/debian3, chromium+firefox; then consolidate all six worktrees, run the full suite, and land via `.claude/scripts/ship.sh land`.
