fix(agents): preserve spawned session cwd at ingress (#162308)

Visible child sessions persist lineage and a managed cwd without an inherited workspace. Keep that cwd for subsequent runs while preserving explicit inherited workspace precedence and sandbox ownership checks. Existing session rows require no migration.
This commit is contained in:
Peter Steinberger 2026-09-30 23:42:58 -07:00 • committed by GitHub
parent 03137c1d72
commit 43d29bbdb1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 12 additions and 4 deletions

View file

@ -31,6 +31,9 @@ configured workspace when it is selected with `worktree: true`. This authorizes
source preparation, not direct access to that host directory. Arbitrary external
`cwd` values and direct project bindings remain restricted.
Visible child sessions keep their managed worktree as the sandbox workspace on
later turns, including sessions created before an update.
The private checkout contains the selected source commit, not the host's shared
Git configuration, credential helpers, other branches, or ignored files selected
by `.worktreeinclude`. Guest preparation does not run the repository's host setup

View file

@ -96,10 +96,10 @@ export function resolveIngressWorkspaceOverrideForSessionRun(
| null,
): string | undefined {
const normalized = normalizeSpawnedRunMetadata(metadata);
if (normalized.spawnedBy) {
if (normalized.spawnedBy && normalized.workspaceDir) {
return normalized.workspaceDir;
}
// Dashboard worktree sessions are not subagents, so their managed cwd is
// also the workspace that sandbox setup must mount on every later turn.
// Visible children can record lineage without an inherited workspace.
// Their managed cwd must remain the sandbox workspace on later turns too.
return normalizeOptionalString(metadata?.cwd);
}

View file

@ -579,12 +579,17 @@ describe("runPreparedReply media-only handling", () => {
]);
});
it("loads configured and canonical workspace skills for managed-worktree sessions", async () => {
it.each([
{ name: "dashboard", spawnedBy: undefined },
{ name: "visible child", spawnedBy: "agent:default:main" },
])("loads workspace skills and runs in the $name managed worktree", async ({ spawnedBy }) => {
const params = baseParams({
sessionKey: "agent:default:dashboard:worktree-session",
workspaceDir: "/tmp/agent-workspace",
sessionEntry: {
sessionId: "session-1",
updatedAt: Date.now(),
spawnedBy,
spawnedCwd: "/tmp/session-worktree",
worktree: {
id: "worktree-1",