Manual release — Push didn't surface this on its own; you found it by checking for updates. Routine improvements like this ship quietly in the background.
Your existing Claude Code session will be saved and resumed automatically after the relaunch — no work lost.
Push 1.8.11
Eight-commit cleanup of the interactive-run lifecycle and the issue↔session resume bridge — one evening's worth of audit work that shook out a stack of latent bugs around Run-in-Terminal, session resume, and the Sessions board's "running" buckets.
What's changed
Run-in-Terminal sessions actually resume now
Pre-fix: clicking Run in Terminal on an issue with prior conversation history started a fresh session every time. The previous sid was sitting in agent_task_sessions but four layers between that table and the spawn command were never wired up:
Interactive run completion endpoints didn't write back to agent_task_sessions (only the background path did), so closing the tab forgot the session.
The workspace-resolve probe computed hasPreviousSession and logged it, then dropped it on the return path.
The Mac always passed sessionId: nil to composeAgentCommand, even when the server knew a prior sid existed.
push session begin --issue X from inside a shell never surfaced the resume command.
All four gaps closed. Run-in-Terminal on an issue with prior history now spawns claude --resume <sid> / codex resume <sid> / opencode --session <sid> / hermes chat --resume <sid> automatically. Auto-resume is silent — matches the background path's behavior. If you want a fresh session, close the tab and start one from outside the issue (a dedicated Run-fresh affordance is deferred until the in-practice friction is observed).
Earlier sessions on issue detail
New Earlier sessions section on issue detail, below the active run / completed-by-run rows. Reuses the same SessionColumnCard used by the Sessions board — same chip layout, same provider glyphs, same resume badge. Filters by issue, excludes the active/completed-by rows above it to avoid double-rendering, sorts newest-first, capped at 10.
Closes MAS-3526.
Session-card resume moved to an explicit terminal-glyph badge
Pre-fix: tapping anywhere on a card in Agent History pushed SessionDetailView — a 250-LOC second-tier view that existed mostly to host a resume button. Tapping a card in Terminal History silently resumed the session. Mis-aimed clicks were doing surprising things.
Now: card body is informational; the explicit terminal-glyph badge in the card header is the only resume affordance. Badge greys out when the run isn't resumable (no sid, or a non-local adapter like gemini / cursor / pi). Terminal (Running) cards still focus the bound tab on tap — the card IS the live tab there, so card == focus is the right gesture.
SessionDetailView deleted (341 LOC). Live transcript surfaces via the bound terminal tab when one exists; per-run log is on the agent's detail page.
Four-CLI auto-type parity for Run-in-Terminal first turn
v1.8.10 documented that Claude and Hermes auto-type /push work <id> [title] on the first turn after a Run-in-Terminal spawn. This release brings Codex and OpenCode to parity:
OpenCode — uses /push work (commands shim resolves the slash dispatch).
Codex — uses $push work (Codex's mention codec rewrites $push to an internal markdown link; trailing text stays free-form prompt content).
Net result: every Run-in-Terminal click on every local-adapter agent now guarantees a first turn, which guarantees a sid bridge back to the heartbeat-runs row, which lights the resume badge instead of leaving the run sid-less and stuck idle at the TUI prompt.
Ghost "Running" cards finally cleared
If you'd been looking at the Sessions board for a few days, you may have noticed cards stuck in Agent (Running) for hours or days after the underlying session was clearly dead. That bucket should have been empty most of the time; instead it accumulated.
Root cause: the server has been writing a 7th heartbeat-run status value (detached) since 2026-04-14, but the shared constants list and the Mac's RunStatus enum only knew about 6. A detached run decoded as the default fallback (queued), which the Mac's session projection routed to Agent (Running). Every reaper-detached run since April has been a ghost.
Fixed by adding detached everywhere: shared constants, Mac enum, both session-projection switches. Swift's exhaustive switch on the enum immediately surfaced both routing sites the moment the case was added — the kind of compile-time invariant that would have caught this on day one if the enum had been complete from the start.
The matching issue-detail string check (isInflight) also left detached out, so the "Running in terminal" pill on issue detail was sticky after the reaper finalized a run server-side. Same fix.
Duplicate interactive-run rows fixed
If you'd run a Mac-side query of heartbeat_runs over the last week, you'd have seen pairs of rows for the same (tab, issue) pair — created 50–60ms apart, identical sessionKey, only one of them ever received a sid PATCH. Same root cause across two failure modes:
Spawn path — Phase 1 of unified-runs (2026-05-08) wired the Mac to create an invocationSource: "interactive" run at Run-in-Terminal spawn time, but didn't inject PUSH_RUN_ID into the spawned shell's env. The skill's push session begin --issue X then fell through env-adoption and POSTed a second row alongside the Mac's. Fixed by reordering the call sites to pre-create the run, then thread the runId into the tab struct and the shell env before spawning.
Caller path — openIssueTab was returning the stale local tab struct after mutating the array element. Callers checked spawnedTab.interactiveRunId == nil on the stale copy, saw nil, and fired a second createInteractiveRun POST as lazy-fallback. Swift struct semantics: the array mutation didn't propagate back to the local variable. Fixed by returning tabs[idx] so post-mutation state is visible.
UUID case-sensitivity regression
A regression introduced and fixed in the same evening. The Mac was writing contextSnapshot.issueId using Swift's UUID.uuidString (uppercase). The server's JSONB JOIN was string equality, and Postgres issues.id::text produces lowercase canonical form. Every Mac-created run between the regression and the fix lost its issue linkage on the wire — run.issue came back null in listRuns, the new "Earlier sessions" filter missed them, and the resolve-workspace previous-session lookup couldn't find them either.
Same case-sensitive comparison existed in four other server-side JOINs — all five now cast both sides to ::uuid, which normalizes regardless of input case. Mac-side defense-in-depth: write .uuidString.lowercased() so future clients also send canonical form.
Workspace-runtime agents now surface their provider on the Sessions board
workspace_runtime — the default agent on every workspace, is_default = true, hidden from GET /agents per design — was producing runs whose adapter type couldn't be derived on the Mac. The Sessions board fell back to the run's adapterType field, but the heartbeat.list query didn't LEFT-JOIN agents, so that field came back blank too. Net: the resume badge greyed out on every workspace_runtime run, even genuinely-resumable Claude ones.
Fix: LEFT JOIN on agents in both heartbeat.list and getRunWithIssue, mirroring the symmetric /live-runs endpoint. Now the provider and the resume badge resolve correctly.
Stale-tab keep-alive cleanup
If you'd force-quit Push (or used dev-rebuild during iteration), restored-from-disk terminal tabs whose Ghostty surface was no longer materialized were keeping their server-side run rows alive forever — the keep-alive PATCHes fired regardless of whether the agent process was actually running. Now gated on the actual presence of a Ghostty surface for the tab.
Plus: reconcileInteractiveRunsOnLaunch now actively fires complete(status: "detached") on rows still running whose tab no longer exists locally. The previous app process is gone, so the agent process is gone too. Cross-machine safety: the loop only iterates over THIS Mac's local tabs, so it can never close another machine's active session.
The reaper's threshold for invocationSource: "interactive" runs is also shortened from 1h to 15min as a backstop for cases where the Mac lost its tab binding entirely (closeTab fired complete fire-and-forget but the POST didn't land before Push exited).
Notes for operators
This release is Manual — invisible to Sparkle's scheduled background timer. Sparkle picks it up when you click Push → Check for Updates in the menu bar. Most of these fixes only matter the next time you click Run in Terminal anyway, so reaching for the menu is the natural moment.
The duplicate-row fix is forward-only — existing duplicate pairs in your DB aren't auto-cleaned. They're harmless (only one of each pair was ever live), but visible in heartbeat.list queries. A separate one-shot dedupe script is future work if it ever matters.
Sessions-board's Agent (Running) bucket may shrink visibly on first launch after upgrade as previously-ghost detached runs route to Agent History where they belong.
One known gap remains (Gap 2 from the audit thread): the closeTab race where a session that produced a sid marker right as the user closed the tab can still lose the sid because the complete POST fires before the FSEvents/marker drain. Separate fix.
Investigation + audit documents in the writing repo:
investigations/2026-05-15-issue-session-resume-audit.md — the four gaps (A/B/C/D), audit method, verification.
investigations/2026-05-15-issue-session-relationship-audit.md — duplicate-row analysis + the Phase 1 PUSH_RUN_ID gap.
investigations/2026-05-15-evening-session-bug-cascade.md — full eight-commit story arc.