fix(sdk): resolve symlinked workspace cwd scope in SessionRouter endpoint reads - #4639
Conversation
…oint reads The broker's session index stores the lifecycle caller's lexical cwd in locator.repo (reconcileReadyScope re-scopes only that field) while locator.stateRoot stays the host process's physical path, because the host derives it from process.cwd(), which resolves symlinks. #readEndpoint derived the expected state root from repo and compared spellings with plain path.resolve equality, so any symlinked cwd made the scope test fail on every reconcile: the adopted attachment was retired, attachment() stayed null through the ACP agent's publication poll, and session/new surfaced "lost exact Router authority" before tearing down a healthy host. Observed end to end with Paseo 0.4.0 driving gjc 0.14.0 over ACP with a symlinked workspace cwd (/home/jun/desk -> /data/Lina-Desk): spawn and initialize ok, session/new error, Models unavailable, Status Error, while the session host had registered and reconciled cleanly and was only killed by the resulting cleanup. The scope test now compares path identity through resolveEquivalentPath, the same symlink-equivalent comparison sameResumeLocator and the index fence-row predicate already use. Confidence: high Scope-risk: narrow Reversibility: trivial Tested: bun test packages/coding-agent/test/sdk-session-router-authority.test.ts (41 pass); live ACP e2e session/new+session/close with a symlinked cwd, its realpath, and a plain cwd Not-tested: Windows reparse-point workspace cwds
|
This PR is superseded by #4645 (maintainer successor, exact same patch). Why: the PR base Equivalence proof for #4645 vs this PR's head This PR will be closed in favor of #4645, which carries the refreshed verification (focused tests, 10/10 adversarial authority controls, live ACP e2e for symlinked + canonical cwds, package check, state-writers gate) and drives the merge for #4637. — |
|
Superseded by maintainer successor #4645 on current The successor preserves @thisisjun786's original commit authorship and credits the original analysis, patch, regression test, and live ACP/Paseo diagnosis. Equivalence is proven by identical stable patch-id Terminal delivery continues only in #4645; closing this duplicate mutation path to preserve one-item/one-owner discipline. — |
What
SessionRouter#readEndpointnow compares the indexed state root against the repo-derived default/chat state roots by path identity (resolveEquivalentPath) instead of lexicalpath.resolvespelling equality.Why
GJC 0.14.0 ACP
session/newfails withlost exact Router authorityfor every symlinked workspace cwd, making the provider unusable from external ACP clients (observed with Paseo 0.4.0: spawn ok, initialize ok, session/new error, Models unavailable, Status Error). Restarting the daemon and the SDK broker does not help.The session index deliberately stores two different spellings for one session (
reconcileReadyScope,packages/coding-agent/src/sdk/broker/lifecycle.ts):locator.repo— the lifecycle caller's lexical cwd (re-scoped tolaunch.cwd; the comment there documents the macOS/var -> /private/varcase),locator.stateRoot— the host process's physical path, because the host derives it fromprocess.cwd(), which resolves symlinks (session-runtime.ts:2700,2981).#readEndpointderived the expected state root fromrepoand compared the two spellings with plainpath.resolveequality. Withcwd = /home/jun/desk -> /data/Lina-Desk:Every reconcile then retires the adopted attachment (
#reconcileadopted branch →#retireAttachment),attachment(id)stays null through the ACP agent's 40×50 ms publication poll, andacp-agent.ts#attachEndpointthrowslost exact Router authority— after which the cleanup tears down a healthy, fully registered host (index showshost_registered→host_heartbeat→record_reconciledall clean before the failure).The broker side is already symlink-tolerant (
sameResumeLocatorusesresolveEquivalentPath, and the 0.14.0 index fence-row predicate was itself introduced to fix "a plainpath.resolvematch … under a symlinked agent dir") — this gate was the one place still comparing spellings.Testing
publishes an attachment whose indexed repo is a symlinked spelling of the state root— fails ondev(clients: 0, attachment never created), passes with the fix; full suitebun test packages/coding-agent/test/sdk-session-router-authority.test.ts→ 41 pass.initialize→session/new→session/close):/home/jun/desk:session/new ERROR: lost exact Router authoritybefore →session/new OK+session/close okafter/data/Lina-Deskand plain cwd/home/jun/tmp: pass before and after (no regression)paseo provider diagnostic gjc) after the local patch:spawn/initialize/session-new/cleanupallok,Models: 185,Status: Ready(was: session/new error, Models failed, Status Error).bun --cwd=packages/coding-agent run check(biome + tsc) clean.GJC verdict
Author-submitted fix; no independent review of this exact head yet, so the verdict is needs-human per the template.
devbun checkpasses (biome +tsc --noEmitonpackages/coding-agent; CI state gates green)Notes
cleanup_pending: reparse_point.