Skip to content

fix(ci): disable uv cache for release-consistency job - #6

Merged
NetDevAutomate merged 1 commit into
mainfrom
fix/ci-release-consistency-cache
Aug 4, 2026
Merged

fix(ci): disable uv cache for release-consistency job#6
NetDevAutomate merged 1 commit into
mainfrom
fix/ci-release-consistency-cache

Conversation

@NetDevAutomate

Copy link
Copy Markdown
Owner

Fixes CI on main, which went red immediately after #5 merged (run 30917393142).

What broke

The release-consistency job failed — but not on its actual check:

✓ Run python scripts/check-release-consistency.py
X Post Run astral-sh/setup-uv@c771a70e...
Cache path /home/runner/work/_temp/setup-uv-cache does not exist on disk.
This likely indicates that there are no dependencies to cache.
Consider disabling the cache input if it is not needed.

The check passed; the cleanup step failed, which still fails the job.

Why

release-consistency runs only scripts/check-release-consistency.py, which is stdlib-only (tomllib, re, argparse, pathlib). It never invokes uv — it installs setup-uv purely to get a Python 3.12 interpreter on PATH. So no uv cache directory is ever created, and setup-uv v9 treats "asked to save a cache that does not exist" as an error where v4 did not.

Introduced by the setup-uv v4 → v9 bump in #5. Worth noting it passed on that PR's run and failed on the post-merge run of the same code — it depends on whether a cache happened to be restored first, so it is timing-dependent rather than deterministic. Not something the PR checks would reliably have caught.

Fix

enable-cache: false on that job only — exactly what the error message recommends. Audited every other job:

Job Invokes uv? Cache
lint, typecheck, sast, audit, test uv sync keep
build, install-smoke uv build via build-release.sh keep
release-consistency never disabled

release-consistency is the only job in any workflow that pulls in setup-uv without ever running uv, so this is the complete set.

Deliberately not switching it to actions/setup-python, which would be the more structurally honest fix: it changes how the job gets its interpreter and risks a python vs python3 difference, for no benefit beyond skipping a ~1s uv download. Noted as a possible tidy-up, not done here.

Verification

  • python scripts/check-release-consistency.py passes locally
  • ci.yml parses, and the rendered step options are {'python-version': '3.12', 'enable-cache': False}
  • CI on this PR should show all 9 jobs green

The Failed to save: Unable to reserve cache ... another job may be creating this cache annotations on test/audit/sast are a pre-existing concurrency warning between parallel jobs sharing one cache key. Warnings, not failures, and present before #5. Left alone.

The release-consistency job runs only check-release-consistency.py, which
is stdlib-only (tomllib/re/argparse) and never invokes uv. No uv cache
directory is therefore created.

setup-uv v9 fails its post step when told to save a cache path that does
not exist on disk:

  Cache path /home/runner/work/_temp/setup-uv-cache does not exist on disk.

The check itself passed; only the cleanup step failed, which still failed
the job. This surfaced after bumping setup-uv v4 -> v9 in #5. It passed on
that PR's run and failed on the post-merge main run, so it is
timing-dependent on whether a cache was restored, not deterministic.

Set enable-cache: false for this job only. Every other setup-uv job runs
uv sync or uv build and legitimately populates the cache.
Copilot AI lite review requested due to automatic review settings August 4, 2026 14:14

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a CI failure in the release-consistency job by preventing astral-sh/setup-uv from attempting to save a cache that is never created in that job (since it only runs a stdlib-only Python script and does not invoke uv).

Changes:

  • Disabled setup-uv caching for the release-consistency job via enable-cache: false.
  • Added inline rationale in the workflow explaining why caching must be disabled for this specific job.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@NetDevAutomate
NetDevAutomate merged commit 4f5ed78 into main Aug 4, 2026
10 checks passed
@NetDevAutomate
NetDevAutomate deleted the fix/ci-release-consistency-cache branch August 4, 2026 14:17
NetDevAutomate added a commit that referenced this pull request Sep 3, 2026
…cache

fix(ci): disable uv cache for release-consistency job
NetDevAutomate added a commit that referenced this pull request Sep 3, 2026
POST /api/session/start now routes through transport=pty by default,
calling active.acquire(config, PTYTransport) and returning a ws_url
the frontend feeds to /api/session/ws. The legacy tmux+ttyd path is
reachable via transport=ttyd in the body or STUDYLOOP_TRANSPORT=ttyd
env override (operator kill-switch).

Why: closes the §1.5b gap flagged in Amendment #5 — without this
bridge, the new WS route was reachable by tests only. Now the
full request path works: picker POSTs /session/start → server
acquires active session + PTYTransport → returns ws_url →
browser opens WS and gets live PTY stream.

Transport dispatch:
- Body field ``transport: pty|ttyd`` (optional, default pty).
- STUDYLOOP_TRANSPORT env wins over body — operator emergency
  switch per plan §1.9 without touching clients.
- On pty path: no tmux check, no ttyd spawn, no tmux metadata in
  session_state.json. Writes transport=pty into state for clarity.

Binary missing (503) now returns a structured payload:
{error, agent, binary, install_hint}. Hints cover all five
registered agents (claude, codex, gemini, kiro, opencode) so the
UI — or an agent orchestrating installs — has a concrete next
step. Per-agent "self-heal and retry" is §1.10 follow-up.

Adapter shell wrapping: PTYTransport's build_launch_cmd returns
["/bin/sh", "-c", adapter.launch_cmd(...)]. Adapters legitimately
use pipes/&& (their launch_cmd returns shell strings), so we
execvpe /bin/sh instead of splitting via shlex. Mirrors what
tmux send-keys effectively did.

Tests:
- +5 in test_web_session_start_pty.py: happy path returns ws_url
  and bypasses tmux, pty-is-default when unset, 409 when active,
  503 with install_hint, env=ttyd overrides body=pty (routes to
  legacy branch and trips the tmux check).
- Existing test_web_session.py: 4 legacy guard tests updated to
  pass transport=ttyd explicitly — they were testing tmux-based
  semantics that only apply to that branch now.

1985 passed, 145 deselected (was 1980 → +5 net). Ruff + format clean.

Plan: Amendment #5 "§1.5b REST→PTY bridge" follow-up is now closed.
Amendment #6 (below) documents the contract for §1.7 frontend.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NetDevAutomate added a commit that referenced this pull request Sep 3, 2026
Drops the latest-stable @xterm/xterm UMD bundle, @xterm/addon-fit,
@xterm/addon-webgl, and xterm.css into web/static/vendor/ and loads
them from index.html. No Alpine component yet — that's §1.7 and
ships in its own PR so the frontend iteration can proceed alone.

Why: unblocks §1.7's agentTerminal() component. With the UMD globals
(Terminal, FitAddon, WebglAddon) available at DOMContentLoaded time,
the component can mount into a <div> and pipe the /api/session/ws
stream directly — no ESM, no build step, no service worker rewrites.

Files vendored (versions pinned in index.html):
- xterm-6.0.0.js           (477 KB, UMD)
- xterm-6.0.0.css          (7 KB)
- xterm-addon-fit-0.11.0.js    (1.5 KB)
- xterm-addon-webgl-0.19.0.js  (242 KB)

Load order matters: xterm-6.0.0.js defers first, then the two
addons defer after it. test_xterm_umd_bundles_load_in_correct_order
locks this so a future reorder can't silently break addon init.

Plus Amendment #6 to the refactor plan documenting §1.5b + §1.6
landing and the §1.7 frontend contract (ws_url from /session/start
is the exact string to feed new WebSocket).

Tests: +7 smoke tests (test_web_vendor_xterm.py) — file presence,
size sanity (>1KB JS, >500B CSS), index.html references, and script
load order. 1992 passed total (1985 → 1992). Ruff + format clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NetDevAutomate added a commit that referenced this pull request Sep 3, 2026
…cache

fix(ci): disable uv cache for release-consistency job
NetDevAutomate added a commit that referenced this pull request Sep 3, 2026
POST /api/session/start now routes through transport=pty by default,
calling active.acquire(config, PTYTransport) and returning a ws_url
the frontend feeds to /api/session/ws. The legacy tmux+ttyd path is
reachable via transport=ttyd in the body or STUDYLOOP_TRANSPORT=ttyd
env override (operator kill-switch).

Why: closes the §1.5b gap flagged in Amendment #5 — without this
bridge, the new WS route was reachable by tests only. Now the
full request path works: picker POSTs /session/start → server
acquires active session + PTYTransport → returns ws_url →
browser opens WS and gets live PTY stream.

Transport dispatch:
- Body field ``transport: pty|ttyd`` (optional, default pty).
- STUDYLOOP_TRANSPORT env wins over body — operator emergency
  switch per plan §1.9 without touching clients.
- On pty path: no tmux check, no ttyd spawn, no tmux metadata in
  session_state.json. Writes transport=pty into state for clarity.

Binary missing (503) now returns a structured payload:
{error, agent, binary, install_hint}. Hints cover all five
registered agents (claude, codex, gemini, kiro, opencode) so the
UI — or an agent orchestrating installs — has a concrete next
step. Per-agent "self-heal and retry" is §1.10 follow-up.

Adapter shell wrapping: PTYTransport's build_launch_cmd returns
["/bin/sh", "-c", adapter.launch_cmd(...)]. Adapters legitimately
use pipes/&& (their launch_cmd returns shell strings), so we
execvpe /bin/sh instead of splitting via shlex. Mirrors what
tmux send-keys effectively did.

Tests:
- +5 in test_web_session_start_pty.py: happy path returns ws_url
  and bypasses tmux, pty-is-default when unset, 409 when active,
  503 with install_hint, env=ttyd overrides body=pty (routes to
  legacy branch and trips the tmux check).
- Existing test_web_session.py: 4 legacy guard tests updated to
  pass transport=ttyd explicitly — they were testing tmux-based
  semantics that only apply to that branch now.

1985 passed, 145 deselected (was 1980 → +5 net). Ruff + format clean.

Plan: Amendment #5 "§1.5b REST→PTY bridge" follow-up is now closed.
Amendment #6 (below) documents the contract for §1.7 frontend.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NetDevAutomate added a commit that referenced this pull request Sep 3, 2026
Drops the latest-stable @xterm/xterm UMD bundle, @xterm/addon-fit,
@xterm/addon-webgl, and xterm.css into web/static/vendor/ and loads
them from index.html. No Alpine component yet — that's §1.7 and
ships in its own PR so the frontend iteration can proceed alone.

Why: unblocks §1.7's agentTerminal() component. With the UMD globals
(Terminal, FitAddon, WebglAddon) available at DOMContentLoaded time,
the component can mount into a <div> and pipe the /api/session/ws
stream directly — no ESM, no build step, no service worker rewrites.

Files vendored (versions pinned in index.html):
- xterm-6.0.0.js           (477 KB, UMD)
- xterm-6.0.0.css          (7 KB)
- xterm-addon-fit-0.11.0.js    (1.5 KB)
- xterm-addon-webgl-0.19.0.js  (242 KB)

Load order matters: xterm-6.0.0.js defers first, then the two
addons defer after it. test_xterm_umd_bundles_load_in_correct_order
locks this so a future reorder can't silently break addon init.

Plus Amendment #6 to the refactor plan documenting §1.5b + §1.6
landing and the §1.7 frontend contract (ws_url from /session/start
is the exact string to feed new WebSocket).

Tests: +7 smoke tests (test_web_vendor_xterm.py) — file presence,
size sanity (>1KB JS, >500B CSS), index.html references, and script
load order. 1992 passed total (1985 → 1992). Ruff + format clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NetDevAutomate added a commit that referenced this pull request Sep 3, 2026
POST /api/session/start now routes through transport=pty by default,
calling active.acquire(config, PTYTransport) and returning a ws_url
the frontend feeds to /api/session/ws. The legacy tmux+ttyd path is
reachable via transport=ttyd in the body or STUDYLOOP_TRANSPORT=ttyd
env override (operator kill-switch).

Why: closes the §1.5b gap flagged in Amendment #5 — without this
bridge, the new WS route was reachable by tests only. Now the
full request path works: picker POSTs /session/start → server
acquires active session + PTYTransport → returns ws_url →
browser opens WS and gets live PTY stream.

Transport dispatch:
- Body field ``transport: pty|ttyd`` (optional, default pty).
- STUDYLOOP_TRANSPORT env wins over body — operator emergency
  switch per plan §1.9 without touching clients.
- On pty path: no tmux check, no ttyd spawn, no tmux metadata in
  session_state.json. Writes transport=pty into state for clarity.

Binary missing (503) now returns a structured payload:
{error, agent, binary, install_hint}. Hints cover all five
registered agents (claude, codex, gemini, kiro, opencode) so the
UI — or an agent orchestrating installs — has a concrete next
step. Per-agent "self-heal and retry" is §1.10 follow-up.

Adapter shell wrapping: PTYTransport's build_launch_cmd returns
["/bin/sh", "-c", adapter.launch_cmd(...)]. Adapters legitimately
use pipes/&& (their launch_cmd returns shell strings), so we
execvpe /bin/sh instead of splitting via shlex. Mirrors what
tmux send-keys effectively did.

Tests:
- +5 in test_web_session_start_pty.py: happy path returns ws_url
  and bypasses tmux, pty-is-default when unset, 409 when active,
  503 with install_hint, env=ttyd overrides body=pty (routes to
  legacy branch and trips the tmux check).
- Existing test_web_session.py: 4 legacy guard tests updated to
  pass transport=ttyd explicitly — they were testing tmux-based
  semantics that only apply to that branch now.

1985 passed, 145 deselected (was 1980 → +5 net). Ruff + format clean.

Plan: Amendment #5 "§1.5b REST→PTY bridge" follow-up is now closed.
Amendment #6 (below) documents the contract for §1.7 frontend.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NetDevAutomate added a commit that referenced this pull request Sep 3, 2026
Drops the latest-stable @xterm/xterm UMD bundle, @xterm/addon-fit,
@xterm/addon-webgl, and xterm.css into web/static/vendor/ and loads
them from index.html. No Alpine component yet — that's §1.7 and
ships in its own PR so the frontend iteration can proceed alone.

Why: unblocks §1.7's agentTerminal() component. With the UMD globals
(Terminal, FitAddon, WebglAddon) available at DOMContentLoaded time,
the component can mount into a <div> and pipe the /api/session/ws
stream directly — no ESM, no build step, no service worker rewrites.

Files vendored (versions pinned in index.html):
- xterm-6.0.0.js           (477 KB, UMD)
- xterm-6.0.0.css          (7 KB)
- xterm-addon-fit-0.11.0.js    (1.5 KB)
- xterm-addon-webgl-0.19.0.js  (242 KB)

Load order matters: xterm-6.0.0.js defers first, then the two
addons defer after it. test_xterm_umd_bundles_load_in_correct_order
locks this so a future reorder can't silently break addon init.

Plus Amendment #6 to the refactor plan documenting §1.5b + §1.6
landing and the §1.7 frontend contract (ws_url from /session/start
is the exact string to feed new WebSocket).

Tests: +7 smoke tests (test_web_vendor_xterm.py) — file presence,
size sanity (>1KB JS, >500B CSS), index.html references, and script
load order. 1992 passed total (1985 → 1992). Ruff + format clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NetDevAutomate added a commit that referenced this pull request Sep 3, 2026
…cache

fix(ci): disable uv cache for release-consistency job
NetDevAutomate added a commit that referenced this pull request Sep 3, 2026
…cache

fix(ci): disable uv cache for release-consistency job
NetDevAutomate added a commit that referenced this pull request Sep 3, 2026
POST /api/session/start now routes through transport=pty by default,
calling active.acquire(config, PTYTransport) and returning a ws_url
the frontend feeds to /api/session/ws. The legacy tmux+ttyd path is
reachable via transport=ttyd in the body or STUDYLOOP_TRANSPORT=ttyd
env override (operator kill-switch).

Why: closes the §1.5b gap flagged in Amendment #5 — without this
bridge, the new WS route was reachable by tests only. Now the
full request path works: picker POSTs /session/start → server
acquires active session + PTYTransport → returns ws_url →
browser opens WS and gets live PTY stream.

Transport dispatch:
- Body field ``transport: pty|ttyd`` (optional, default pty).
- STUDYLOOP_TRANSPORT env wins over body — operator emergency
  switch per plan §1.9 without touching clients.
- On pty path: no tmux check, no ttyd spawn, no tmux metadata in
  session_state.json. Writes transport=pty into state for clarity.

Binary missing (503) now returns a structured payload:
{error, agent, binary, install_hint}. Hints cover all five
registered agents (claude, codex, gemini, kiro, opencode) so the
UI — or an agent orchestrating installs — has a concrete next
step. Per-agent "self-heal and retry" is §1.10 follow-up.

Adapter shell wrapping: PTYTransport's build_launch_cmd returns
["/bin/sh", "-c", adapter.launch_cmd(...)]. Adapters legitimately
use pipes/&& (their launch_cmd returns shell strings), so we
execvpe /bin/sh instead of splitting via shlex. Mirrors what
tmux send-keys effectively did.

Tests:
- +5 in test_web_session_start_pty.py: happy path returns ws_url
  and bypasses tmux, pty-is-default when unset, 409 when active,
  503 with install_hint, env=ttyd overrides body=pty (routes to
  legacy branch and trips the tmux check).
- Existing test_web_session.py: 4 legacy guard tests updated to
  pass transport=ttyd explicitly — they were testing tmux-based
  semantics that only apply to that branch now.

1985 passed, 145 deselected (was 1980 → +5 net). Ruff + format clean.

Plan: Amendment #5 "§1.5b REST→PTY bridge" follow-up is now closed.
Amendment #6 (below) documents the contract for §1.7 frontend.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NetDevAutomate added a commit that referenced this pull request Sep 3, 2026
Drops the latest-stable @xterm/xterm UMD bundle, @xterm/addon-fit,
@xterm/addon-webgl, and xterm.css into web/static/vendor/ and loads
them from index.html. No Alpine component yet — that's §1.7 and
ships in its own PR so the frontend iteration can proceed alone.

Why: unblocks §1.7's agentTerminal() component. With the UMD globals
(Terminal, FitAddon, WebglAddon) available at DOMContentLoaded time,
the component can mount into a <div> and pipe the /api/session/ws
stream directly — no ESM, no build step, no service worker rewrites.

Files vendored (versions pinned in index.html):
- xterm-6.0.0.js           (477 KB, UMD)
- xterm-6.0.0.css          (7 KB)
- xterm-addon-fit-0.11.0.js    (1.5 KB)
- xterm-addon-webgl-0.19.0.js  (242 KB)

Load order matters: xterm-6.0.0.js defers first, then the two
addons defer after it. test_xterm_umd_bundles_load_in_correct_order
locks this so a future reorder can't silently break addon init.

Plus Amendment #6 to the refactor plan documenting §1.5b + §1.6
landing and the §1.7 frontend contract (ws_url from /session/start
is the exact string to feed new WebSocket).

Tests: +7 smoke tests (test_web_vendor_xterm.py) — file presence,
size sanity (>1KB JS, >500B CSS), index.html references, and script
load order. 1992 passed total (1985 → 1992). Ruff + format clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NetDevAutomate added a commit that referenced this pull request Sep 3, 2026
…cache

fix(ci): disable uv cache for release-consistency job
NetDevAutomate added a commit that referenced this pull request Sep 3, 2026
POST /api/session/start now routes through transport=pty by default,
calling active.acquire(config, PTYTransport) and returning a ws_url
the frontend feeds to /api/session/ws. The legacy tmux+ttyd path is
reachable via transport=ttyd in the body or STUDYLOOP_TRANSPORT=ttyd
env override (operator kill-switch).

Why: closes the §1.5b gap flagged in Amendment #5 — without this
bridge, the new WS route was reachable by tests only. Now the
full request path works: picker POSTs /session/start → server
acquires active session + PTYTransport → returns ws_url →
browser opens WS and gets live PTY stream.

Transport dispatch:
- Body field ``transport: pty|ttyd`` (optional, default pty).
- STUDYLOOP_TRANSPORT env wins over body — operator emergency
  switch per plan §1.9 without touching clients.
- On pty path: no tmux check, no ttyd spawn, no tmux metadata in
  session_state.json. Writes transport=pty into state for clarity.

Binary missing (503) now returns a structured payload:
{error, agent, binary, install_hint}. Hints cover all five
registered agents (claude, codex, gemini, kiro, opencode) so the
UI — or an agent orchestrating installs — has a concrete next
step. Per-agent "self-heal and retry" is §1.10 follow-up.

Adapter shell wrapping: PTYTransport's build_launch_cmd returns
["/bin/sh", "-c", adapter.launch_cmd(...)]. Adapters legitimately
use pipes/&& (their launch_cmd returns shell strings), so we
execvpe /bin/sh instead of splitting via shlex. Mirrors what
tmux send-keys effectively did.

Tests:
- +5 in test_web_session_start_pty.py: happy path returns ws_url
  and bypasses tmux, pty-is-default when unset, 409 when active,
  503 with install_hint, env=ttyd overrides body=pty (routes to
  legacy branch and trips the tmux check).
- Existing test_web_session.py: 4 legacy guard tests updated to
  pass transport=ttyd explicitly — they were testing tmux-based
  semantics that only apply to that branch now.

1985 passed, 145 deselected (was 1980 → +5 net). Ruff + format clean.

Plan: Amendment #5 "§1.5b REST→PTY bridge" follow-up is now closed.
Amendment #6 (below) documents the contract for §1.7 frontend.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NetDevAutomate added a commit that referenced this pull request Sep 3, 2026
Drops the latest-stable @xterm/xterm UMD bundle, @xterm/addon-fit,
@xterm/addon-webgl, and xterm.css into web/static/vendor/ and loads
them from index.html. No Alpine component yet — that's §1.7 and
ships in its own PR so the frontend iteration can proceed alone.

Why: unblocks §1.7's agentTerminal() component. With the UMD globals
(Terminal, FitAddon, WebglAddon) available at DOMContentLoaded time,
the component can mount into a <div> and pipe the /api/session/ws
stream directly — no ESM, no build step, no service worker rewrites.

Files vendored (versions pinned in index.html):
- xterm-6.0.0.js           (477 KB, UMD)
- xterm-6.0.0.css          (7 KB)
- xterm-addon-fit-0.11.0.js    (1.5 KB)
- xterm-addon-webgl-0.19.0.js  (242 KB)

Load order matters: xterm-6.0.0.js defers first, then the two
addons defer after it. test_xterm_umd_bundles_load_in_correct_order
locks this so a future reorder can't silently break addon init.

Plus Amendment #6 to the refactor plan documenting §1.5b + §1.6
landing and the §1.7 frontend contract (ws_url from /session/start
is the exact string to feed new WebSocket).

Tests: +7 smoke tests (test_web_vendor_xterm.py) — file presence,
size sanity (>1KB JS, >500B CSS), index.html references, and script
load order. 1992 passed total (1985 → 1992). Ruff + format clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NetDevAutomate added a commit that referenced this pull request Sep 3, 2026
…cache

fix(ci): disable uv cache for release-consistency job
NetDevAutomate added a commit that referenced this pull request Sep 3, 2026
POST /api/session/start now routes through transport=pty by default,
calling active.acquire(config, PTYTransport) and returning a ws_url
the frontend feeds to /api/session/ws. The legacy tmux+ttyd path is
reachable via transport=ttyd in the body or STUDYLOOP_TRANSPORT=ttyd
env override (operator kill-switch).

Why: closes the §1.5b gap flagged in Amendment #5 — without this
bridge, the new WS route was reachable by tests only. Now the
full request path works: picker POSTs /session/start → server
acquires active session + PTYTransport → returns ws_url →
browser opens WS and gets live PTY stream.

Transport dispatch:
- Body field ``transport: pty|ttyd`` (optional, default pty).
- STUDYLOOP_TRANSPORT env wins over body — operator emergency
  switch per plan §1.9 without touching clients.
- On pty path: no tmux check, no ttyd spawn, no tmux metadata in
  session_state.json. Writes transport=pty into state for clarity.

Binary missing (503) now returns a structured payload:
{error, agent, binary, install_hint}. Hints cover all five
registered agents (claude, codex, gemini, kiro, opencode) so the
UI — or an agent orchestrating installs — has a concrete next
step. Per-agent "self-heal and retry" is §1.10 follow-up.

Adapter shell wrapping: PTYTransport's build_launch_cmd returns
["/bin/sh", "-c", adapter.launch_cmd(...)]. Adapters legitimately
use pipes/&& (their launch_cmd returns shell strings), so we
execvpe /bin/sh instead of splitting via shlex. Mirrors what
tmux send-keys effectively did.

Tests:
- +5 in test_web_session_start_pty.py: happy path returns ws_url
  and bypasses tmux, pty-is-default when unset, 409 when active,
  503 with install_hint, env=ttyd overrides body=pty (routes to
  legacy branch and trips the tmux check).
- Existing test_web_session.py: 4 legacy guard tests updated to
  pass transport=ttyd explicitly — they were testing tmux-based
  semantics that only apply to that branch now.

1985 passed, 145 deselected (was 1980 → +5 net). Ruff + format clean.

Plan: Amendment #5 "§1.5b REST→PTY bridge" follow-up is now closed.
Amendment #6 (below) documents the contract for §1.7 frontend.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NetDevAutomate added a commit that referenced this pull request Sep 3, 2026
Drops the latest-stable @xterm/xterm UMD bundle, @xterm/addon-fit,
@xterm/addon-webgl, and xterm.css into web/static/vendor/ and loads
them from index.html. No Alpine component yet — that's §1.7 and
ships in its own PR so the frontend iteration can proceed alone.

Why: unblocks §1.7's agentTerminal() component. With the UMD globals
(Terminal, FitAddon, WebglAddon) available at DOMContentLoaded time,
the component can mount into a <div> and pipe the /api/session/ws
stream directly — no ESM, no build step, no service worker rewrites.

Files vendored (versions pinned in index.html):
- xterm-6.0.0.js           (477 KB, UMD)
- xterm-6.0.0.css          (7 KB)
- xterm-addon-fit-0.11.0.js    (1.5 KB)
- xterm-addon-webgl-0.19.0.js  (242 KB)

Load order matters: xterm-6.0.0.js defers first, then the two
addons defer after it. test_xterm_umd_bundles_load_in_correct_order
locks this so a future reorder can't silently break addon init.

Plus Amendment #6 to the refactor plan documenting §1.5b + §1.6
landing and the §1.7 frontend contract (ws_url from /session/start
is the exact string to feed new WebSocket).

Tests: +7 smoke tests (test_web_vendor_xterm.py) — file presence,
size sanity (>1KB JS, >500B CSS), index.html references, and script
load order. 1992 passed total (1985 → 1992). Ruff + format clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NetDevAutomate added a commit that referenced this pull request Sep 5, 2026
POST /api/session/start now routes through transport=pty by default,
calling active.acquire(config, PTYTransport) and returning a ws_url
the frontend feeds to /api/session/ws. The legacy tmux+ttyd path is
reachable via transport=ttyd in the body or STUDYLOOP_TRANSPORT=ttyd
env override (operator kill-switch).

Why: closes the §1.5b gap flagged in Amendment #5 — without this
bridge, the new WS route was reachable by tests only. Now the
full request path works: picker POSTs /session/start → server
acquires active session + PTYTransport → returns ws_url →
browser opens WS and gets live PTY stream.

Transport dispatch:
- Body field ``transport: pty|ttyd`` (optional, default pty).
- STUDYLOOP_TRANSPORT env wins over body — operator emergency
  switch per plan §1.9 without touching clients.
- On pty path: no tmux check, no ttyd spawn, no tmux metadata in
  session_state.json. Writes transport=pty into state for clarity.

Binary missing (503) now returns a structured payload:
{error, agent, binary, install_hint}. Hints cover all five
registered agents (claude, codex, gemini, kiro, opencode) so the
UI — or an agent orchestrating installs — has a concrete next
step. Per-agent "self-heal and retry" is §1.10 follow-up.

Adapter shell wrapping: PTYTransport's build_launch_cmd returns
["/bin/sh", "-c", adapter.launch_cmd(...)]. Adapters legitimately
use pipes/&& (their launch_cmd returns shell strings), so we
execvpe /bin/sh instead of splitting via shlex. Mirrors what
tmux send-keys effectively did.

Tests:
- +5 in test_web_session_start_pty.py: happy path returns ws_url
  and bypasses tmux, pty-is-default when unset, 409 when active,
  503 with install_hint, env=ttyd overrides body=pty (routes to
  legacy branch and trips the tmux check).
- Existing test_web_session.py: 4 legacy guard tests updated to
  pass transport=ttyd explicitly — they were testing tmux-based
  semantics that only apply to that branch now.

1985 passed, 145 deselected (was 1980 → +5 net). Ruff + format clean.

Plan: Amendment #5 "§1.5b REST→PTY bridge" follow-up is now closed.
Amendment #6 (below) documents the contract for §1.7 frontend.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NetDevAutomate added a commit that referenced this pull request Sep 5, 2026
Drops the latest-stable @xterm/xterm UMD bundle, @xterm/addon-fit,
@xterm/addon-webgl, and xterm.css into web/static/vendor/ and loads
them from index.html. No Alpine component yet — that's §1.7 and
ships in its own PR so the frontend iteration can proceed alone.

Why: unblocks §1.7's agentTerminal() component. With the UMD globals
(Terminal, FitAddon, WebglAddon) available at DOMContentLoaded time,
the component can mount into a <div> and pipe the /api/session/ws
stream directly — no ESM, no build step, no service worker rewrites.

Files vendored (versions pinned in index.html):
- xterm-6.0.0.js           (477 KB, UMD)
- xterm-6.0.0.css          (7 KB)
- xterm-addon-fit-0.11.0.js    (1.5 KB)
- xterm-addon-webgl-0.19.0.js  (242 KB)

Load order matters: xterm-6.0.0.js defers first, then the two
addons defer after it. test_xterm_umd_bundles_load_in_correct_order
locks this so a future reorder can't silently break addon init.

Plus Amendment #6 to the refactor plan documenting §1.5b + §1.6
landing and the §1.7 frontend contract (ws_url from /session/start
is the exact string to feed new WebSocket).

Tests: +7 smoke tests (test_web_vendor_xterm.py) — file presence,
size sanity (>1KB JS, >500B CSS), index.html references, and script
load order. 1992 passed total (1985 → 1992). Ruff + format clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants