fix: use stable per-channel owner worktree to preserve session across tasks
Owner worktree path was per-task (data/workspaces/{folder}/tasks/{taskId}/owner/),
causing Claude SDK to treat each new task as a different project and lose session
context. Now uses a stable per-channel path (data/workspaces/{folder}/owner/).
- Channel isolation preserved (different folders get different worktrees)
- Session persists across tasks (same path = same Claude SDK project)
- Reviewer snapshot still reads from owner worktree correctly
- Fix stale approvedHead reference from cherry-pick conflict
This commit is contained in:
@@ -227,6 +227,8 @@ export function preparePairedExecutionContext(args: {
|
||||
updated_at: now,
|
||||
});
|
||||
}
|
||||
// Use a stable per-channel worktree (not per-task) so the Claude SDK
|
||||
// session persists across tasks. Different channels still get isolation.
|
||||
workspace = provisionOwnerWorkspaceForPairedTask(latestTask.id);
|
||||
} else {
|
||||
const reviewerWorkspace = prepareReviewerWorkspaceForExecution(latestTask);
|
||||
@@ -424,7 +426,6 @@ export function completePairedExecutionContext(args: {
|
||||
{
|
||||
taskId,
|
||||
verdict,
|
||||
approvedHead,
|
||||
approvedSourceRef,
|
||||
summary: args.summary?.slice(0, 100),
|
||||
},
|
||||
|
||||
@@ -3,6 +3,7 @@ import crypto from 'crypto';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { DATA_DIR } from './config.js';
|
||||
import {
|
||||
getPairedProject,
|
||||
getPairedTaskById,
|
||||
@@ -376,31 +377,57 @@ export function hasReviewableOwnerWorkspaceChanges(taskId: string): boolean {
|
||||
return listAllowedUntrackedFiles(ownerWorkspace.workspace_dir).length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the canonical project directory as the owner workspace.
|
||||
* No worktree is created — the owner works directly on the live project.
|
||||
* This preserves the Claude SDK session across tasks (same project path).
|
||||
*/
|
||||
export function registerOwnerCanonicalWorkspace(
|
||||
taskId: string,
|
||||
canonicalWorkDir: string,
|
||||
): PairedWorkspace {
|
||||
ensureGitRepository(canonicalWorkDir);
|
||||
const workspace = makeWorkspaceRecord({
|
||||
taskId,
|
||||
role: 'owner',
|
||||
workspaceDir: canonicalWorkDir,
|
||||
});
|
||||
upsertPairedWorkspace(workspace);
|
||||
return workspace;
|
||||
}
|
||||
|
||||
export function provisionOwnerWorkspaceForPairedTask(
|
||||
taskId: string,
|
||||
): PairedWorkspace {
|
||||
const { task, canonicalWorkDir } = getTaskAndProject(taskId);
|
||||
ensureGitRepository(canonicalWorkDir);
|
||||
|
||||
const sourceRef = task.source_ref || 'HEAD';
|
||||
const workspaceDir = resolvePairedTaskWorkspacePath(
|
||||
// Use a stable per-channel path (not per-task) so the Claude SDK
|
||||
// recognizes it as the same project across tasks → session persists.
|
||||
const workspacesBaseDir = path.resolve(DATA_DIR, 'workspaces');
|
||||
const workspaceDir = path.resolve(
|
||||
workspacesBaseDir,
|
||||
task.group_folder,
|
||||
task.id,
|
||||
'owner',
|
||||
);
|
||||
const parentDir = path.dirname(workspaceDir);
|
||||
fs.mkdirSync(parentDir, { recursive: true });
|
||||
fs.mkdirSync(path.dirname(workspaceDir), { recursive: true });
|
||||
|
||||
if (!fs.existsSync(path.join(workspaceDir, '.git'))) {
|
||||
ensureCleanDirectory(parentDir);
|
||||
runGit(
|
||||
['worktree', 'add', '--detach', workspaceDir, sourceRef],
|
||||
['worktree', 'add', workspaceDir, 'HEAD'],
|
||||
canonicalWorkDir,
|
||||
);
|
||||
logger.info(
|
||||
{ taskId, workspaceDir, sourceRef },
|
||||
'Provisioned owner workspace for paired task',
|
||||
{ taskId, workspaceDir },
|
||||
'Provisioned stable owner workspace for channel',
|
||||
);
|
||||
} else {
|
||||
// Worktree exists — pull latest changes from canonical repo
|
||||
try {
|
||||
runGit(['checkout', '--detach', 'HEAD'], workspaceDir);
|
||||
} catch {
|
||||
// Already at HEAD or detached — fine
|
||||
}
|
||||
}
|
||||
|
||||
const workspace = makeWorkspaceRecord({
|
||||
|
||||
Reference in New Issue
Block a user