fix(easd): unblock runs stranded in an abandoned Coding session - #16
Open
Wyn2004 wants to merge 2 commits into
Open
fix(easd): unblock runs stranded in an abandoned Coding session#16Wyn2004 wants to merge 2 commits into
Wyn2004 wants to merge 2 commits into
Conversation
An EASD run was bound to the session_id of whatever Coding session started
its first lifecycle phase, and every later phase call re-asserted that exact
binding. Once the user moved to a new session the run was unusable, with no
recovery short of a manual SQL UPDATE.
The ownership assertion was duplicated ten times across the lifecycle
functions instead of living in the shared _session_for_run helper, so the
gate could not be relaxed in one place. Fold it into that helper, raising a
structured TraceSessionMismatch, and drop the ten copies. Add
rebind_run_session so a user can explicitly move a non-terminal run to the
session they are in, refusing the move when another run already owns the
target session (uq_trace_runs_active_session). Surface the mismatch as a
409 easd_session_mismatch payload and expose POST /easd/runs/{id}/rebind.
The frontend turns that payload into an inline "Adopt run" affordance
instead of an opaque error line, leaving the user to re-trigger the phase
once the run follows them.
Every phase action hands the run off to its own session_id, so a run stranded in an abandoned session never produced the mismatch rejection the adoption banner was keyed on — the affordance was unreachable in practice. Derive the mismatch from the run and the open session directly, keeping the rejected-call path as a fallback for callers that do pass a foreign session.
Wyn2004
force-pushed
the
fix/easd-run-orphaned-session-rebind
branch
from
August 31, 2026 11:29
d0ba1e4 to
aee5622
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
An EASD run is bound to the
session_idof whatever Coding session started its first lifecycle phase, and every later phase call re-asserts that exact binding. Once the user moves to a new Coding session the run is unusable — start/retry authoring, planning, review, verification and recovery all reject it — with no recovery short of a manualUPDATE trace_runs SET session_id = …against SQLite.Reported symptom:
EASD authoring run belongs to another Coding session.Root cause
The ownership assertion was duplicated ten times across the lifecycle functions in
app/services/trace_service.py(submit_authored_specification,submit_authored_plan,start_plan_authoring_in_session,retry_plan_authoring_in_session,start_run_in_session,start_spec_authoring_in_session,retry_spec_authoring_in_session,start_review_in_session,start_verification_in_session,recover_run_in_session) rather than living in the shared_session_for_runhelper those functions already call. That helper only validated the target session's project/workspace — never ownership — so the gate could not be relaxed in one place, and no rebind mechanism existed anywhere in the backend or the frontend.ChatSessionhas noended_at/statuscolumn, so "the old session is closed" is not a fact the system can observe. The fix therefore treats the user's explicit adoption as the only signal, rather than inferring abandonment.What changed
Backend
_session_for_run, raising a structuredTraceSessionMismatch(carryingrun_id+current_session_id), and delete the ten duplicated checks._resolve_target_sessionkeeps the ownership-free project/workspace validation for callers that legitimately accept a foreign session.rebind_run_session, which moves a non-terminal run to the caller's session. It refuses terminal runs, and refuses a target session another run already owns —uq_trace_runs_active_sessionis a partial unique index, so without that check the move surfaces as an unhandledIntegrityError(500) instead of a conflict.409 {"code": "easd_session_mismatch", …}via_raise_easd, and exposePOST /api/easd/runs/{run_id}/rebind.Frontend
EasdSessionMismatchApiError+ detection ineasdResponse,rebindEasdRun,useRebindEasdRunMutation.run.session_id !== openSessionId) with the rejected-call path kept as a fallback. Adoption does not auto-replay the phase action; the existing Start/Retry button re-enables once the run follows the user.Checklist
IntegrityError/500 path)ruff check/ruff format --check/ty checkcleanpytestgreen (44 passed)lint/typecheckclean,test:unitgreen (444 passed)buildsucceedsEvidence
Backend — before the fix
The reproduction, run against pristine
mainin a throwaway worktree:The new tests, run against the same pristine
main:Backend — after the fix
API — live repro against a real run
Run
06a95505…created and bound to session06a8407e…, then driven from a second Coding session06a95506…in the same project:UI — before
Same run, same Coding session, pre-fix frontend served from a pristine
mainworktree. The run header offersOpen drafting chat/Retry drafting, both of which hand off to the original session — there is no indication the run belongs elsewhere and no way to continue it here.UI — after
Identical navigation. The header now states the mismatch and offers
Adopt run; after adopting, the banner disappears, the run'ssession_idfollows the user, and the phase actions work in place.Screenshots are hosted on
pr-evidence/easd-session-rebind, following the existingpr-evidence-hostconvention — that branch is not meant to be merged into a product branch.Notes for the reviewer
converged/cancelledruns, and for a session another run already owns.