Skip to content

fix(ci): resolve failing typecheck and audit jobs - #5

Merged
NetDevAutomate merged 2 commits into
mainfrom
fix/ci-typecheck-and-audit
Aug 4, 2026
Merged

fix(ci): resolve failing typecheck and audit jobs#5
NetDevAutomate merged 2 commits into
mainfrom
fix/ci-typecheck-and-audit

Conversation

@NetDevAutomate

Copy link
Copy Markdown
Owner

Fixes both failing jobs on main (run 29416495485).

typecheck — 21 reportMissingImports errors

uv sync --all-packages installs each workspace package's default dependencies but not its extras. studyctl keeps fastapi in the web extra, so pyright could not resolve fastapi anywhere under src/studyctl/web/.

Added --all-extras to the sync step that precedes pyright. With extras installed, pyright reports 0 errors for both packages — the web code typechecks cleanly, so no suppressions were needed.

publish.yml and pre-release.yml ran pyright after the same incomplete sync and carried the identical latent defect. They are fixed too, otherwise the next release would have failed the same way.

audit — 65 advisories

uv audit failed on 65 advisories across 15 locked packages. Upgraded only those packages, deliberately not uv lock --upgrade: a blanket upgrade also bumps ruff, which then reports 124 new lint errors — trading a red audit job for a red lint job.

uv audit now reports no known vulnerabilities in 194 packages.

Node 20 deprecation (bonus)

actions/checkout v4.2.2 -> v7.0.1 and astral-sh/setup-uv v4 -> v9.0.0 across all workflows. Both targeted Node 20 and were already being force-run on Node 24; this clears the warning before it becomes an error. Existing pin style preserved per file. Reviewed release notes: checkout v5-v7 and setup-uv v9 have no breaking changes affecting this repo's usage.

Verification

Every CI job run locally against this branch:

Job Result
lint All checks passed / 269 files formatted
typecheck 0 errors both packages
audit no known vulnerabilities, 194 packages
sast 0 bandit issues
test 1730 passed, 44 skipped
release-consistency OK
build wheel + sdist built
install-smoke exit 0

starlette crosses a major version (0.52.1 -> 1.3.1), which CI's test selection does not cover because the web tests are e2e-marked. Verified separately: app constructs with 23 routes and /api/courses, /api/history, /, /openapi.json all return 200 under starlette 1.x. The 3 Playwright failures in test_web_terminal.py reproduce identically on the pre-upgrade lock, so they are pre-existing and unrelated.

Follow-ups not included

  • nightly-install.yml, nightly-uat.yml and pre-release.yml pass --extra dev --extra test, but studyctl defines no such extras (dev is a dependency group). uv silently ignores unknown extras and exits 0, so those jobs are quietly installing less than intended.
  • The test job does not install extras, so src/studyctl/web/ is typechecked but never exercised by unit tests.
  • audit hard-fails main on any new advisory in any transitive dependency, so main can go red without a code change. Worth considering a scheduled run or a documented triage path.

The audit job runs `uv audit`, which failed on 65 advisories across 15
locked packages (urllib3, starlette, pyjwt, python-multipart, authlib,
click, cryptography, idna, mcp, pydantic-settings, pymdown-extensions,
pytest, setuptools, torch, transformers).

Upgrade only those packages rather than running a blanket `uv lock
--upgrade`: a full upgrade also bumps ruff, which then reports 124 new
lint errors and would trade a red audit job for a red lint job.

`uv audit` now reports no known vulnerabilities in 194 packages.
The typecheck job synced with `uv sync --all-packages`, which installs
each workspace package's default dependencies but not its extras.
studyctl keeps fastapi in the `web` extra, so pyright could not resolve
`fastapi` in src/studyctl/web/ and failed with 21 reportMissingImports
errors.

Add --all-extras to the sync step that precedes pyright. publish.yml and
pre-release.yml ran pyright after the same incomplete sync, so they carried
the identical latent defect and would have failed on the next release.

Also bump actions/checkout v4.2.2 -> v7.0.1 and astral-sh/setup-uv v4 -> v9.0.0
across all workflows. Both targeted Node 20, which is deprecated and currently
force-run on Node 24; this removes the warning before it becomes an error.
Existing pin style is preserved per file (SHA pins in ci.yml and nightly-uat.yml,
floating tags elsewhere).
Copilot AI lite review requested due to automatic review settings August 4, 2026 14:04
@NetDevAutomate
NetDevAutomate merged commit 5d5561e into main Aug 4, 2026
10 of 11 checks passed
@NetDevAutomate
NetDevAutomate deleted the fix/ci-typecheck-and-audit branch August 4, 2026 14:08

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 addresses failing CI on main by adjusting uv dependency installation to satisfy pyright’s import resolution, updating the lockfile to eliminate uv audit advisories, and bumping GitHub Actions used in workflows to newer Node-compatible versions.

Changes:

  • Update CI/release workflows to install uv extras (and bump actions/checkout / setup-uv) so pyright can resolve studyctl’s optional web dependencies.
  • Refresh uv.lock with targeted dependency upgrades to clear uv audit advisories.
  • Apply the same workflow fixes across release-related workflows (publish.yml, pre-release.yml) and other scheduled/docs workflows for consistency.

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
uv.lock Updates locked dependencies to resolve audit advisories and refresh resolution metadata.
.github/workflows/ci.yml Adjusts typecheck dependency sync and updates action versions.
.github/workflows/publish.yml Ensures release quality gate installs needed deps for pyright; updates action versions.
.github/workflows/pre-release.yml Aligns pre-release gate dependency installation with pyright needs; updates action versions.
.github/workflows/docs.yml Updates action versions used for docs deployment workflow.
.github/workflows/nightly-install.yml Updates action versions for nightly install checks.
.github/workflows/nightly-uat.yml Updates action versions for nightly UAT workflow.

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

Comment on lines 36 to 39

- name: Install workspace dependencies
run: uv sync --all-packages --group dev
run: uv sync --all-packages --all-extras --group dev

Comment on lines 23 to 26

- name: Install workspace dependencies
run: uv sync --all-packages --group dev
run: uv sync --all-packages --all-extras --group dev

Comment thread .github/workflows/ci.yml
Comment on lines 26 to 29
with:
python-version: "3.12"
- run: uv sync --all-packages
- run: uv sync --all-packages --all-extras
- run: cd packages/agent-session-tools && uv run pyright src/
@NetDevAutomate

Copy link
Copy Markdown
Owner Author

Correction to the third follow-up bullet above

The first follow-up bullet in the PR description is wrong and should be disregarded. It claimed:

nightly-install.yml, nightly-uat.yml and pre-release.yml pass --extra dev --extra test, but studyctl defines no such extras (dev is a dependency group). uv silently ignores unknown extras and exits 0, so those jobs are quietly installing less than intended.

Both halves are incorrect:

  1. dev and test are real extras. packages/agent-session-tools/pyproject.toml defines them under [project.optional-dependencies] (lines 69 and 75), alongside tokens/tui/watch/semantic. So --extra dev --extra test resolves and installs correctly.

  2. uv does not silently ignore unknown extras. It hard-errors with exit code 2:

    error: Extra `definitely-not-an-extra` is not defined in any project's `optional-dependencies` table
    

The bad inference came from reading $? after a pipeline (uv sync ... | head -8), which reports head's status, not uv's. Verified properly by redirecting to a file first.

What is actually true, and it strengthens the fix

uv syncs the dev dependency-group by default:

uv sync --all-packages                      -> pytest-playwright PRESENT
uv sync --all-packages --no-default-groups  -> pytest-playwright MISSING

Two consequences:

  • --group dev is a no-op in publish.yml, pre-release.yml and the test job. Harmless, but it implies dev is opt-in when it is not.
  • This is precisely why the typecheck failure was about extras, not groups. Groups arrive free; extras never do, and --all-packages does not change that. That is the actual root cause, and --all-extras is the correct and minimal fix.

The other two follow-ups in the description stand: the test job still does not install extras (so src/studyctl/web/ is typechecked but never exercised), and audit can still turn main red without a code change.

NetDevAutomate added a commit that referenced this pull request Sep 3, 2026
fix(ci): resolve failing typecheck and audit jobs
NetDevAutomate added a commit that referenced this pull request Sep 3, 2026
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.
NetDevAutomate added a commit that referenced this pull request Sep 3, 2026
/api/session/ws now runs the new transport stack — no more
agent_session_manager, no legacy {type:"start"} frame. The route binds
the already-acquired active.ActiveSession to the WS and pumps transport
events both ways under asyncio.TaskGroup + except*.

Why: §1.5 of the agent-session-transport plan. Closes the wiring gap
that left session/transport.py, session/transports/pty.py, and
session/active.py fully implemented but unreachable from the web UI
since PR-B-1..B-4.

Route contract (authoritative for frontend §1.7):
- Preconditions: active session must already be acquired. WS closes
  1008 pre-accept when current() is None.
- Origin guard: localhost/127.0.0.1 any port by default, extend via
  STUDYLOOP_ALLOWED_ORIGINS env var (plan Blocker B1).
- Session-id match: ?study_session_id must equal active.study_session_id
  or close 1008 (pattern from terminal_proxy.py:134).
- Inbound JSON: input/resize/stop control frames.
- Outbound: binary for OutputBytes, text JSON for lifecycle events.
- finally: always await active.release() — guarantees a clean
  singleton on disconnect.

Scope cuts (deferred to §1.5b, separate PR):
- POST /session/start still runs legacy tmux+ttyd flow. Re-pointing
  it at active.acquire(config, PTYTransport) is the remaining §1.5
  work; user deferred because it needs Playwright-driven iteration.
- Frontend WS feature flag (§1.9) becomes a browser concern rather
  than server concern — the WS route no longer serves ttyd at all;
  iframe path stays behind /terminal/ in terminal_proxy.py.

Legacy test cleanup: 2 stale tests removed from test_web_live_session.py
(test_live_session_websocket_streams_events_and_accepts_input and
test_live_session_websocket_rejects_acp_until_handshake_is_implemented)
— they exercised the now-replaced protocol. test_session_runtime.py's
unit tests of the legacy manager still pass; session_runtime/ stays
on disk until §1.5b also lands.

Tests: 1980 passed, 145 deselected. +8 new in test_web_session_ws.py
(origin guard, session-id mismatch, no-active-session rejection,
Started/Output/Stopped pump, input/resize/stop inbound frames,
release-on-disconnect). Ruff clean, format clean.

Plan: docs/plans/2026-05-09-refactor-agent-session-transport-plan.md
Amendment #5 appended with the full route 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
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
fix(ci): resolve failing typecheck and audit jobs
NetDevAutomate added a commit that referenced this pull request Sep 3, 2026
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.
NetDevAutomate added a commit that referenced this pull request Sep 3, 2026
/api/session/ws now runs the new transport stack — no more
agent_session_manager, no legacy {type:"start"} frame. The route binds
the already-acquired active.ActiveSession to the WS and pumps transport
events both ways under asyncio.TaskGroup + except*.

Why: §1.5 of the agent-session-transport plan. Closes the wiring gap
that left session/transport.py, session/transports/pty.py, and
session/active.py fully implemented but unreachable from the web UI
since PR-B-1..B-4.

Route contract (authoritative for frontend §1.7):
- Preconditions: active session must already be acquired. WS closes
  1008 pre-accept when current() is None.
- Origin guard: localhost/127.0.0.1 any port by default, extend via
  STUDYLOOP_ALLOWED_ORIGINS env var (plan Blocker B1).
- Session-id match: ?study_session_id must equal active.study_session_id
  or close 1008 (pattern from terminal_proxy.py:134).
- Inbound JSON: input/resize/stop control frames.
- Outbound: binary for OutputBytes, text JSON for lifecycle events.
- finally: always await active.release() — guarantees a clean
  singleton on disconnect.

Scope cuts (deferred to §1.5b, separate PR):
- POST /session/start still runs legacy tmux+ttyd flow. Re-pointing
  it at active.acquire(config, PTYTransport) is the remaining §1.5
  work; user deferred because it needs Playwright-driven iteration.
- Frontend WS feature flag (§1.9) becomes a browser concern rather
  than server concern — the WS route no longer serves ttyd at all;
  iframe path stays behind /terminal/ in terminal_proxy.py.

Legacy test cleanup: 2 stale tests removed from test_web_live_session.py
(test_live_session_websocket_streams_events_and_accepts_input and
test_live_session_websocket_rejects_acp_until_handshake_is_implemented)
— they exercised the now-replaced protocol. test_session_runtime.py's
unit tests of the legacy manager still pass; session_runtime/ stays
on disk until §1.5b also lands.

Tests: 1980 passed, 145 deselected. +8 new in test_web_session_ws.py
(origin guard, session-id mismatch, no-active-session rejection,
Started/Output/Stopped pump, input/resize/stop inbound frames,
release-on-disconnect). Ruff clean, format clean.

Plan: docs/plans/2026-05-09-refactor-agent-session-transport-plan.md
Amendment #5 appended with the full route 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
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
/api/session/ws now runs the new transport stack — no more
agent_session_manager, no legacy {type:"start"} frame. The route binds
the already-acquired active.ActiveSession to the WS and pumps transport
events both ways under asyncio.TaskGroup + except*.

Why: §1.5 of the agent-session-transport plan. Closes the wiring gap
that left session/transport.py, session/transports/pty.py, and
session/active.py fully implemented but unreachable from the web UI
since PR-B-1..B-4.

Route contract (authoritative for frontend §1.7):
- Preconditions: active session must already be acquired. WS closes
  1008 pre-accept when current() is None.
- Origin guard: localhost/127.0.0.1 any port by default, extend via
  STUDYLOOP_ALLOWED_ORIGINS env var (plan Blocker B1).
- Session-id match: ?study_session_id must equal active.study_session_id
  or close 1008 (pattern from terminal_proxy.py:134).
- Inbound JSON: input/resize/stop control frames.
- Outbound: binary for OutputBytes, text JSON for lifecycle events.
- finally: always await active.release() — guarantees a clean
  singleton on disconnect.

Scope cuts (deferred to §1.5b, separate PR):
- POST /session/start still runs legacy tmux+ttyd flow. Re-pointing
  it at active.acquire(config, PTYTransport) is the remaining §1.5
  work; user deferred because it needs Playwright-driven iteration.
- Frontend WS feature flag (§1.9) becomes a browser concern rather
  than server concern — the WS route no longer serves ttyd at all;
  iframe path stays behind /terminal/ in terminal_proxy.py.

Legacy test cleanup: 2 stale tests removed from test_web_live_session.py
(test_live_session_websocket_streams_events_and_accepts_input and
test_live_session_websocket_rejects_acp_until_handshake_is_implemented)
— they exercised the now-replaced protocol. test_session_runtime.py's
unit tests of the legacy manager still pass; session_runtime/ stays
on disk until §1.5b also lands.

Tests: 1980 passed, 145 deselected. +8 new in test_web_session_ws.py
(origin guard, session-id mismatch, no-active-session rejection,
Started/Output/Stopped pump, input/resize/stop inbound frames,
release-on-disconnect). Ruff clean, format clean.

Plan: docs/plans/2026-05-09-refactor-agent-session-transport-plan.md
Amendment #5 appended with the full route 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
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
fix(ci): resolve failing typecheck and audit jobs
NetDevAutomate added a commit that referenced this pull request Sep 3, 2026
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.
NetDevAutomate added a commit that referenced this pull request Sep 3, 2026
fix(ci): resolve failing typecheck and audit jobs
NetDevAutomate added a commit that referenced this pull request Sep 3, 2026
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.
NetDevAutomate added a commit that referenced this pull request Sep 3, 2026
/api/session/ws now runs the new transport stack — no more
agent_session_manager, no legacy {type:"start"} frame. The route binds
the already-acquired active.ActiveSession to the WS and pumps transport
events both ways under asyncio.TaskGroup + except*.

Why: §1.5 of the agent-session-transport plan. Closes the wiring gap
that left session/transport.py, session/transports/pty.py, and
session/active.py fully implemented but unreachable from the web UI
since PR-B-1..B-4.

Route contract (authoritative for frontend §1.7):
- Preconditions: active session must already be acquired. WS closes
  1008 pre-accept when current() is None.
- Origin guard: localhost/127.0.0.1 any port by default, extend via
  STUDYLOOP_ALLOWED_ORIGINS env var (plan Blocker B1).
- Session-id match: ?study_session_id must equal active.study_session_id
  or close 1008 (pattern from terminal_proxy.py:134).
- Inbound JSON: input/resize/stop control frames.
- Outbound: binary for OutputBytes, text JSON for lifecycle events.
- finally: always await active.release() — guarantees a clean
  singleton on disconnect.

Scope cuts (deferred to §1.5b, separate PR):
- POST /session/start still runs legacy tmux+ttyd flow. Re-pointing
  it at active.acquire(config, PTYTransport) is the remaining §1.5
  work; user deferred because it needs Playwright-driven iteration.
- Frontend WS feature flag (§1.9) becomes a browser concern rather
  than server concern — the WS route no longer serves ttyd at all;
  iframe path stays behind /terminal/ in terminal_proxy.py.

Legacy test cleanup: 2 stale tests removed from test_web_live_session.py
(test_live_session_websocket_streams_events_and_accepts_input and
test_live_session_websocket_rejects_acp_until_handshake_is_implemented)
— they exercised the now-replaced protocol. test_session_runtime.py's
unit tests of the legacy manager still pass; session_runtime/ stays
on disk until §1.5b also lands.

Tests: 1980 passed, 145 deselected. +8 new in test_web_session_ws.py
(origin guard, session-id mismatch, no-active-session rejection,
Started/Output/Stopped pump, input/resize/stop inbound frames,
release-on-disconnect). Ruff clean, format clean.

Plan: docs/plans/2026-05-09-refactor-agent-session-transport-plan.md
Amendment #5 appended with the full route 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
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
fix(ci): resolve failing typecheck and audit jobs
NetDevAutomate added a commit that referenced this pull request Sep 3, 2026
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.
NetDevAutomate added a commit that referenced this pull request Sep 3, 2026
/api/session/ws now runs the new transport stack — no more
agent_session_manager, no legacy {type:"start"} frame. The route binds
the already-acquired active.ActiveSession to the WS and pumps transport
events both ways under asyncio.TaskGroup + except*.

Why: §1.5 of the agent-session-transport plan. Closes the wiring gap
that left session/transport.py, session/transports/pty.py, and
session/active.py fully implemented but unreachable from the web UI
since PR-B-1..B-4.

Route contract (authoritative for frontend §1.7):
- Preconditions: active session must already be acquired. WS closes
  1008 pre-accept when current() is None.
- Origin guard: localhost/127.0.0.1 any port by default, extend via
  STUDYLOOP_ALLOWED_ORIGINS env var (plan Blocker B1).
- Session-id match: ?study_session_id must equal active.study_session_id
  or close 1008 (pattern from terminal_proxy.py:134).
- Inbound JSON: input/resize/stop control frames.
- Outbound: binary for OutputBytes, text JSON for lifecycle events.
- finally: always await active.release() — guarantees a clean
  singleton on disconnect.

Scope cuts (deferred to §1.5b, separate PR):
- POST /session/start still runs legacy tmux+ttyd flow. Re-pointing
  it at active.acquire(config, PTYTransport) is the remaining §1.5
  work; user deferred because it needs Playwright-driven iteration.
- Frontend WS feature flag (§1.9) becomes a browser concern rather
  than server concern — the WS route no longer serves ttyd at all;
  iframe path stays behind /terminal/ in terminal_proxy.py.

Legacy test cleanup: 2 stale tests removed from test_web_live_session.py
(test_live_session_websocket_streams_events_and_accepts_input and
test_live_session_websocket_rejects_acp_until_handshake_is_implemented)
— they exercised the now-replaced protocol. test_session_runtime.py's
unit tests of the legacy manager still pass; session_runtime/ stays
on disk until §1.5b also lands.

Tests: 1980 passed, 145 deselected. +8 new in test_web_session_ws.py
(origin guard, session-id mismatch, no-active-session rejection,
Started/Output/Stopped pump, input/resize/stop inbound frames,
release-on-disconnect). Ruff clean, format clean.

Plan: docs/plans/2026-05-09-refactor-agent-session-transport-plan.md
Amendment #5 appended with the full route 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
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
fix(ci): resolve failing typecheck and audit jobs
NetDevAutomate added a commit that referenced this pull request Sep 3, 2026
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.
NetDevAutomate added a commit that referenced this pull request Sep 3, 2026
/api/session/ws now runs the new transport stack — no more
agent_session_manager, no legacy {type:"start"} frame. The route binds
the already-acquired active.ActiveSession to the WS and pumps transport
events both ways under asyncio.TaskGroup + except*.

Why: §1.5 of the agent-session-transport plan. Closes the wiring gap
that left session/transport.py, session/transports/pty.py, and
session/active.py fully implemented but unreachable from the web UI
since PR-B-1..B-4.

Route contract (authoritative for frontend §1.7):
- Preconditions: active session must already be acquired. WS closes
  1008 pre-accept when current() is None.
- Origin guard: localhost/127.0.0.1 any port by default, extend via
  STUDYLOOP_ALLOWED_ORIGINS env var (plan Blocker B1).
- Session-id match: ?study_session_id must equal active.study_session_id
  or close 1008 (pattern from terminal_proxy.py:134).
- Inbound JSON: input/resize/stop control frames.
- Outbound: binary for OutputBytes, text JSON for lifecycle events.
- finally: always await active.release() — guarantees a clean
  singleton on disconnect.

Scope cuts (deferred to §1.5b, separate PR):
- POST /session/start still runs legacy tmux+ttyd flow. Re-pointing
  it at active.acquire(config, PTYTransport) is the remaining §1.5
  work; user deferred because it needs Playwright-driven iteration.
- Frontend WS feature flag (§1.9) becomes a browser concern rather
  than server concern — the WS route no longer serves ttyd at all;
  iframe path stays behind /terminal/ in terminal_proxy.py.

Legacy test cleanup: 2 stale tests removed from test_web_live_session.py
(test_live_session_websocket_streams_events_and_accepts_input and
test_live_session_websocket_rejects_acp_until_handshake_is_implemented)
— they exercised the now-replaced protocol. test_session_runtime.py's
unit tests of the legacy manager still pass; session_runtime/ stays
on disk until §1.5b also lands.

Tests: 1980 passed, 145 deselected. +8 new in test_web_session_ws.py
(origin guard, session-id mismatch, no-active-session rejection,
Started/Output/Stopped pump, input/resize/stop inbound frames,
release-on-disconnect). Ruff clean, format clean.

Plan: docs/plans/2026-05-09-refactor-agent-session-transport-plan.md
Amendment #5 appended with the full route 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
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
/api/session/ws now runs the new transport stack — no more
agent_session_manager, no legacy {type:"start"} frame. The route binds
the already-acquired active.ActiveSession to the WS and pumps transport
events both ways under asyncio.TaskGroup + except*.

Why: §1.5 of the agent-session-transport plan. Closes the wiring gap
that left session/transport.py, session/transports/pty.py, and
session/active.py fully implemented but unreachable from the web UI
since PR-B-1..B-4.

Route contract (authoritative for frontend §1.7):
- Preconditions: active session must already be acquired. WS closes
  1008 pre-accept when current() is None.
- Origin guard: localhost/127.0.0.1 any port by default, extend via
  STUDYLOOP_ALLOWED_ORIGINS env var (plan Blocker B1).
- Session-id match: ?study_session_id must equal active.study_session_id
  or close 1008 (pattern from terminal_proxy.py:134).
- Inbound JSON: input/resize/stop control frames.
- Outbound: binary for OutputBytes, text JSON for lifecycle events.
- finally: always await active.release() — guarantees a clean
  singleton on disconnect.

Scope cuts (deferred to §1.5b, separate PR):
- POST /session/start still runs legacy tmux+ttyd flow. Re-pointing
  it at active.acquire(config, PTYTransport) is the remaining §1.5
  work; user deferred because it needs Playwright-driven iteration.
- Frontend WS feature flag (§1.9) becomes a browser concern rather
  than server concern — the WS route no longer serves ttyd at all;
  iframe path stays behind /terminal/ in terminal_proxy.py.

Legacy test cleanup: 2 stale tests removed from test_web_live_session.py
(test_live_session_websocket_streams_events_and_accepts_input and
test_live_session_websocket_rejects_acp_until_handshake_is_implemented)
— they exercised the now-replaced protocol. test_session_runtime.py's
unit tests of the legacy manager still pass; session_runtime/ stays
on disk until §1.5b also lands.

Tests: 1980 passed, 145 deselected. +8 new in test_web_session_ws.py
(origin guard, session-id mismatch, no-active-session rejection,
Started/Output/Stopped pump, input/resize/stop inbound frames,
release-on-disconnect). Ruff clean, format clean.

Plan: docs/plans/2026-05-09-refactor-agent-session-transport-plan.md
Amendment #5 appended with the full route 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
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>
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