Skip to content

fix(session): a web session whose server stopped is ended, not reattachable - #47

Merged
NetDevAutomate merged 2 commits into
mainfrom
fix/stale-web-session-state
Sep 26, 2026
Merged

NetDevAutomate merged 2 commits into
mainfrom
fix/stale-web-session-state

Conversation

@NetDevAutomate

Copy link
Copy Markdown
Owner

Summary

After studyloop web restarted, going back to the Study view showed This session reports transport "pty", which this view cannot render. That is the one transport the view renders with xterm.

A browser-terminal or ACP session runs inside the server process that started it, and the state file records that process as pid. When the server stopped without ending the session, the file kept mode=focus. /session/start already treats such a claim as stale (claim_blocks_web_start, the crash-then-restart cell). /api/session/state, holding no slot, served the file verbatim with no reattach_url. The console's load-time adoption then fell into its "unavailable" fallback, which named the transport as the problem.

Changes

  • session_state.web_claim_is_orphaned(): a pty/acp claim whose pid is neither this process nor alive. It never judges CLI sessions (their pid is not their liveness), this process's own in-flight reservation, or claims with no pid.
  • _get_full_state() reports an orphaned web claim as mode=ended and deletes nothing. An unended session's parking lot holds unflushed learner notes. This covers /api/session/state and the SSE stream.
  • live-agent-console.js: a known transport with no connection URL now reads The server did not return a connection for this session. The "cannot render" sentence stays for unrecognised transports. That is what docs/troubleshooting.md already described; the sentence was unreachable because every caller passes a transport.
  • Troubleshooting section and CHANGELOG.

RED 4953b52f (4 failing for the stated reasons, 6 guards passing) -> GREEN c0812286.

Verification

  • The touched session suites pass 170/170: slot reconcile, session state, authority matrix, web session, lifecycle, live banner, start purpose.
  • JS passes 167/167, docs-policing tests pass 571, mkdocs strict is clean, and ruff, format and pyright report nothing.

Not in this PR

  • The dead session's study_sessions row stays open (ended_at NULL). The endpoint reports it ended but does not write.
  • The invalid agent config: kiro_default.json line in kiro-cli's TUI is unrelated to StudyLoop. kiro-cli 2.24.0 refuses a user config named after its reserved built-in agent and falls back to the built-in.

…or reattach

A PTY/ACP session lives inside the `studyloop web` process that started it,
and the state file records that process as `pid`. When the server stops
without ending the session, the file keeps `mode=focus`. /session/start
already treats such a claim as stale (claim_blocks_web_start: the
crash-then-restart cell), but /api/session/state held no slot, fell back to
the file verbatim and reported the session live with no reattach_url. The
console's load-time adoption then called start() with transport "pty" and
no URL, and _mountUnavailable told the learner: 'This session reports
transport "pty", which this view cannot render' -- about the transport it
renders with xterm. Seen live after restarting `studyloop web --lan`; the
sessions DB still holds the dead session open (ended_at NULL since 23 Sep).

RED, each for the stated reason:
- /api/session/state reports a dead-pid pty session, and an acp one, as
  `focus` (2 tests);
- the console says it "cannot render" pty and acp when only the URL is
  missing (2 tests).

Guards that pass now and must keep passing: a live foreign server's session,
this process's own in-flight reservation, and a CLI session (whose pid is
not its liveness) are left alone; nothing is deleted (an unended session's
parking lot is unflushed learner notes); an unrecognised transport still
names what the view cannot render.
…chable

`_get_full_state()` cleared a dead CLI (tmux) session but had no rule for a
web-owned one, and read_session_state() drops tmux keys for pty/acp files,
so a browser-terminal or ACP session whose server died kept reporting
`mode=focus` forever. /api/session/state held no slot, served that file
verbatim with no reattach_url, and the console adopted it into its
"unavailable" fallback.

- session_state.web_claim_is_orphaned(): the read-side twin of
  claim_blocks_web_start's crash-then-restart cell -- a pty/acp claim whose
  pid is neither this process nor alive. CLI sessions (pid is not their
  liveness), this process's own in-flight reservation, and pid-less claims
  are never judged orphaned.
- _get_full_state(): an orphaned web claim is reported `mode=ended`, the
  verdict /session/start already reached. Nothing is deleted: an unended
  session never flushed its parking lot. Covers /api/session/state and the
  SSE dashboard stream, which share the reader.
- live-agent-console.js: a known transport (pty/acp) with no connection URL
  now reads "The server did not return a connection for this session"; the
  "cannot render" sentence is kept for transports the view does not render.
  That sentence is what docs/troubleshooting.md already promised for this
  case; every caller passes a transport, so it was unreachable.
- docs/troubleshooting.md: the stopped-server case. CHANGELOG Unreleased/Fixed.
Copilot AI lite review requested due to automatic review settings September 26, 2026 15:19
@NetDevAutomate NetDevAutomate added this to the 0.5.x milestone Sep 26, 2026

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.

Copilot review overview

🟡 Changes recommended

PID liveness can misclassify unreaped zombie processes, leaving stale sessions exposed.

Review effort: Lite
Findings: 1 Low severity

Open (1)
What changed in this PR

Fixes stale web-session recovery by marking orphaned PTY/ACP sessions as ended and improving unavailable-console messaging.

Changes:

  • Detects orphaned web claims using server PID liveness.
  • Preserves session state and learner notes.
  • Adds regression tests and documentation updates.
File Description
packages/​studyloop/​tests/​test_session_slot_reconcile.py Tests orphaned-session behavior.
packages/​studyloop/​tests/​js/​live-agent-console-unavailable.test.js Tests frontend fallback messages.
packages/​studyloop/​src/​studyloop/​web/​static/​js/​components/​live-agent-console.js Clarifies missing connection errors.
packages/​studyloop/​src/​studyloop/​web/​routes/​session/​_ipc.py Reports orphaned claims as ended.
packages/​studyloop/​src/​studyloop/​session_state.py Adds web-claim PID liveness detection.
docs/​troubleshooting.md Documents stopped-server behavior.
CHANGELOG.md Records the fix.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.


def test_a_cli_session_is_left_alone(self, client: TestClient) -> None:
"""A CLI session's pid is the CLI process, which can exit while its
multiplexer session lives on; the zombie rule judges CLI sessions."""
@NetDevAutomate
NetDevAutomate merged commit c081228 into main Sep 26, 2026
17 checks passed
@NetDevAutomate
NetDevAutomate deleted the fix/stale-web-session-state branch September 28, 2026 14:59
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