ADR 006: WSL2 as a Worker Host Class — Generalizing windows-dev, Gated on #6185

Status: Proposed. No implementation lands in this PR — this ADR records a plan and a hard prerequisite gate; follow-up issues are opened only after the user reviews and approves.

Date: 2026-08-12 Relates: ADR 005 (windows-dev Docker/CF-Tunnel design, which this ADR generalizes and gates), #6110 (Docker implementation, merged), #6163 (dispatcher CF-Tunnel/docker-run wiring, merged — PR 6174), #6185 (live, unresolved WSL2 instability — the hard blocker this ADR is sequenced behind), #6186 (CF Tunnel provisioning), docs/environments/ windows-dev.md, docs/environments/wsl1.md (superseded native-process design), docs/playbooks/ provision-windows-dev-docker-worker.md.

Context

ADR-005 designed windows-dev as one specific host running Docker-based ephemeral workers over a CF-Tunnel-inbound SSH path. That design is built and merged (#6110, #6163). Trying to actually go live on 2026-08-12 surfaced two problems this ADR addresses:

  1. windows-dev’s WSL2 instance is currently unstable, live, root cause unknown (#6185). Direct, real-time diagnosis today found: cloudflared restarting on a bursty (not steady-interval) pattern, correlated loosely with wsl.exe invocation activity but not fully explained by it (a 44-minute fully-idle window had zero events, then a fresh cycle fired with no trigger); a keep-alive systemd unit (sleep infinity, meant to test the “distro went idle” theory) was itself torn down and recreated mid-test, ruling out simple idle-detection as the mechanism; zero correlating Windows-side event-log entries (System/Application, Hyper-V-VmSwitch) in the exact window of a cycle boundary; VHDX free space is not the cause (948G/1007G free). Root cause remains open after this investigation — it needs the on-host Windows-side deep-dive #6185 already scopes (Event Viewer history, WSL build/update correlation, background-software audit), which a Claude Code session could not complete with the tools available today.
  2. The dev_r_server_capacity rows for both windows-dev and wsl1 still point at retired, NXDOMAIN two-level CF Tunnel hostnames (*.tunnel.zintegrowana.online, retired 2026-08-11 per docs/playbooks/fleet-backup-ssh-cf-tunnel.md — Cloudflare’s free Universal SSL only covers one subdomain level). The live, correctly-provisioned tunnels use single-level hostnames (windows-dev-ssh.zintegrowana.online, wsl1-ssh.zintegrowana.online). This was latent, not yet harmful (enabled=false on both rows stops the dispatcher from ever trying to connect), but would have caused a silent dispatch failure the moment either row was enabled without this fix.

Neither problem is specific to windows-dev’s identity as a machine — they’re both instances of a more general gap: this repo has never defined what “a WSL2-based worker host” must satisfy before it’s trusted with real dispatch weight. ADR-005 designed windows-dev’s Docker/CF-Tunnel mechanism in detail, but treated it as a one-off for this specific workstation. wsl1 already exists as a second, currently-superseded WSL2 host (architecturally different design, retired per ADR-005’s own reconciliation note). If either windows-dev recovers or a future WSL2 host is added, the same class of gaps (stale tunnel config, unvalidated stability, ad-hoc provisioning) will recur unless the requirements are written down once, generically.

Decision

Define “WSL2 worker host” as a host class with an explicit readiness contract, gated behind fixing #6185 as a hard Phase 0 prerequisite. windows-dev is the first (and currently only) instance of this class; the contract is written generically so a future WSL2 host (a revived wsl1, a new workstation) can be onboarded by satisfying the same checklist rather than re-deriving it.

This ADR does not redesign the Docker/CF-Tunnel mechanism itself — ADR-005 §5b/§3 already cover that and remain the mechanism of record. This ADR adds the class-level contract around that mechanism: what must be true before any WSL2 host (windows-dev or otherwise) is allowed to carry real dispatch weight, and how to keep more than one such host from drifting out of sync the way windows-dev’s and wsl1’s tunnel hostnames already did.

Phase 0 — Fix #6185 (hard prerequisite, blocks everything below)

No phase below starts until #6185 has an identified root cause and a verified-stable window (the existing issue’s own suggested next steps: Windows Event Viewer Kernel-General/System log correlation at a cycle boundary, a background-software audit beyond what this session could check non-interactively, WSL build/update version history). Verification bar: cloudflared (or an equivalent long-running service) must stay continuously active for at least 30 minutes of mixed idle and light-activity conditions before Phase 1 is considered unblocked — the existing 10-minute bar in provision-windows-dev-docker-worker.md’s soak procedure is raised here because today’s investigation showed a 4.5-minute quiet window with no events followed immediately by a fresh cycle, i.e. 10 minutes of quiet is not by itself strong evidence of stability given the bursty pattern observed.

Do not attempt to route real dispatch traffic to windows-dev (or design a second WSL2 host) while #6185 is open. A host that silently drops SSH/container sessions every 1–10 minutes cannot safely run an ephemeral worker to completion regardless of how well the Docker/CF-Tunnel mechanism itself is built.

Phase 1 — WSL2 worker host readiness contract

Once #6185’s fix is verified (Phase 0), any host claiming membership in the “WSL2 worker host” class must satisfy all of the following before dev_r_server_capacity.enabled is set true for it:

#RequirementRationale
1Native docker-ce installed inside the distro (not Docker Desktop)ADR-005 §5b — licensing fit, headless-appropriate
2/etc/wsl.conf has [boot] systemd=trueRequired for the CF Tunnel and any future systemd-managed worker services
3CF Tunnel hostname is single-level (<host>-ssh.zintegrowana.online), never the retired two-level formDirect cause of today’s latent bug — Cloudflare’s free Universal SSL constraint, documented in fleet-backup-ssh-cf-tunnel.md
4dev_r_server_capacity.ssh_host matches the actually live tunnel hostname, verified by a DNS resolution check as part of onboarding, not assumed from a naming conventionToday’s bug was exactly this assumption going stale silently
5A stability soak of at least 30 minutes (Phase 0’s raised bar) completed and recorded in the host’s provisioning issue before enabled=truePrevents onboarding a host that merely looks stable in a short window
6Worker image builds and smoke-tests clean (node --version, claude --version at minimum)Baseline sanity before trusting it with real jobs
7claude_accounts entries each have their own independent claude auth login — never a copied .credentials.jsonStanding rule, CLAUDE.md §Claude Code OAuth Token Refresh, applies identically to every host class
8Role-scoped secret mounting per ADR-005 §8 (secret-manager-role containers only get the age key)No new secrets policy per host class — reuse the existing one
9Host registered in dev_r_services per CLAUDE.md’s compliance rule, with a real docs/environments/<host>.md hardware spec (not copied from another host)Compliance + avoids the “assumed same ceiling as windows-dev” mistake ADR-005 §5a already corrected once for lap1

A known follow-up debt this table does not fix by itself: ADR-005’s own §7 already flagged that docs/environments/windows-dev.md, this ADR itself, and several other files use the retired two-level hostname convention in prose (13 files, per the 2026-08-12 network-admin agent’s report). Requirement #4’s DNS-verification step is the guard against this recurring — the fix is procedural (verify at onboarding time), not just a one-time doc sweep.

Phase 2 — Apply the contract to windows-dev

Once Phase 0 is cleared:

  1. Fix the dev_r_server_capacity.ssh_host values for windows-dev and wsl1 (both currently stale — see Context §2). This is a data fix, not a design change, and can happen as soon as a session with live-DB-write permission is available; it does not need to wait for #6185 since both rows stay enabled=false regardless.
  2. Run the Phase 1 checklist against windows-dev specifically. Items 1, 6, 7 are already done (this session installed native docker-ce, verified hello-world and claude --version, and no claude auth login has been run yet — item 7 is not yet satisfied, tracked as open). Item 2 (systemd=true) is already set. Item 3/4 need re-verification against the corrected single-level hostname from #6186. Item 5 (30-min soak) has not been attempted — the only WSL2 uptime windows observed so far were unstable by definition.
  3. Only after all nine items are satisfied: set dev_r_server_capacity.enabled=true for windows-dev, starting at the existing conservative max_workers_prime=1, max_weight_prime='light' (ADR-005 §5a — unchanged, this ADR does not revisit that sizing decision).

Phase 3 — Optional: a second WSL2 host

Out of scope to build now, but the point of writing the contract generically: if wsl1 (or a new workstation) is ever revived as a second WSL2 worker host under this same Docker/CF-Tunnel mechanism (not its old, superseded native-process design), it onboards by satisfying the Phase 1 table — no new ADR needed for the mechanism itself, only a provisioning issue that walks the checklist. This ADR does not decide whether wsl1 should be revived; §Consequences below notes the option without recommending it.

Explicitly out of scope

  • Redefining how much direct/ad-hoc work an interactive Windows session (this Claude Code session) should do versus delegating through the issue queue. This came up while scoping this ADR; the owner’s explicit call was to leave that to per-session judgment as today, not encode a rigid rule here. This ADR is about WSL2 host infrastructure, not about interactive-session operating norms.
  • Re-deciding the Docker/CF-Tunnel mechanism itself — ADR-005 §3/§5b stand unchanged.
  • The bms4 proxy (ADR-005 §9) — unrelated to this ADR’s scope.
  • Reviving wsl1 — Phase 3 notes the option exists under this contract; actually doing it is a separate future decision, not part of this plan.

Consequences

Positive

  • A future WSL2 host (revived wsl1, a new machine) onboards against a written checklist instead of re-discovering the same two bugs (stale tunnel hostname, unverified stability) windows-dev hit today.
  • The stability bar is raised from “looked fine for 10 minutes” to “30 minutes including idle windows,” directly responding to today’s evidence that a short quiet window is not reliable evidence of a fixed root cause.
  • Makes explicit that #6185 is not a “nice to fix eventually” bug but a hard gate — no dispatch design work on top of an unstable host is worth doing until the instability itself is understood.

Negative / risks

  • Phase 0 has no committed timeline — #6185’s root cause is genuinely unknown as of this ADR, and the investigation so far has ruled out several plausible causes without finding the real one. This ADR cannot promise when windows-dev becomes usable.
  • The Phase 1 contract is sized against one data point (windows-dev). It may be incomplete — requirements specific to windows-dev’s particular hardware/Windows-build quirks could be mistakenly generalized, or a genuinely host-class-wide requirement could be missing until a second host actually exercises the checklist.
  • Raising the soak bar to 30 minutes delays windows-dev’s go-live further versus the original 10-minute bar in the existing playbook — a deliberate trade-off given today’s evidence, not a cost-free change.

Follow-up issues to open (only after this ADR is approved)

  1. Fix dev_r_server_capacity.ssh_host for windows-dev and wsl1 to the corrected single-level hostnames (Phase 2, item 1). Small, low-risk, blocked today only by needing a session with live-DB-write permission (hit an Auto Mode classifier block in the session that drafted this ADR).
  2. #6185 root-cause investigation, continued (Phase 0) — already open, human-action, this ADR adds no new scope to it beyond raising the post-fix verification bar to 30 minutes.
  3. Update provision-windows-dev-docker-worker.md’s soak procedure to the 30-minute bar (Phase 0) once #6185 is fixed, and add the Phase 1 checklist as this playbook’s own “WSL2 host onboarding” section so it’s actionable there rather than only in this ADR.
  4. Doc sweep for the 13 files still referencing the retired two-level tunnel hostname convention (flagged by the 2026-08-12 network-admin agent, listed under Phase 1’s table note) — mechanical, independent of #6185.

Implementation scope for this ADR

In scope here: this ADR document only. No code, no DB writes, no doc sweep, no #6185 fix attempted by this PR. Follow-up issues above are opened only after the user reviews and approves this design, matching ADR-005’s own pattern.