Skip to content
12 changes: 11 additions & 1 deletion packages/desktop/src/features/queue/TaskQueue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type { Session } from '@snowtree/core/types/session';
import type { ToolPanel } from '@snowtree/core/types/panels';
import type { Database as DatabaseService } from '../../infrastructure/database';
import type { Project } from '../../infrastructure/database';
import { fetchAndCacheRepoInfo } from '../../infrastructure/ipc/git';

interface TaskQueueOptions {
sessionManager: SessionManager;
Expand All @@ -26,6 +27,7 @@ interface TaskQueueOptions {
executionTracker: ExecutionTracker;
worktreeNameGenerator: WorktreeNameGenerator;
getMainWindow: () => Electron.BrowserWindow | null;
gitExecutor: import('../../executors/git').GitExecutor;
}

interface CreateSessionJob {
Expand Down Expand Up @@ -312,7 +314,15 @@ export class TaskQueue {
baseBranch: actualBaseBranch,
statusMessage: undefined,
});


// Initialize git cache for the session
try {
await fetchAndCacheRepoInfo(session.id, worktreePath, sessionManager, this.options.gitExecutor);
} catch (error) {
console.warn(`[TaskQueue] Failed to init git cache for session ${session.id}:`, error);
// Non-fatal, continue with session creation
}

// Attach codexConfig to the session object for the panel creation in events.ts
if (codexConfig) {
(session as Session & { codexConfig?: typeof codexConfig }).codexConfig = codexConfig;
Expand Down
4 changes: 4 additions & 0 deletions packages/desktop/src/features/session/SessionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,10 @@ export class SessionManager extends EventEmitter {
skipContinueNext: dbSession.skip_continue_next || undefined,
claudeSessionId: dbSession.claude_session_id || undefined,
executionMode: normalizedExecutionMode,
currentBranch: dbSession.current_branch || undefined,
ownerRepo: dbSession.owner_repo || undefined,
isFork: dbSession.is_fork || undefined,
originOwnerRepo: dbSession.origin_owner_repo || undefined,
};
}

Expand Down
3 changes: 2 additions & 1 deletion packages/desktop/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,8 @@ async function initializeServices() {
gitDiffManager,
executionTracker,
worktreeNameGenerator,
getMainWindow: () => mainWindow
getMainWindow: () => mainWindow,
gitExecutor
});

const services: AppServices = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ describe('Git IPC Handlers - Repo Info Cache', () => {
});

it('should use cached branch and repo info', async () => {
// Set up cache hit
// Set up cache hit - need BOTH branch AND owner_repo
mockSessionManager.db.getSession.mockReturnValue({
current_branch: 'feature-branch',
owner_repo: 'owner/repo',
Expand All @@ -88,19 +88,7 @@ describe('Git IPC Handlers - Repo Info Cache', () => {
});

mockGitExecutor.run
// 1. git remote get-url upstream (will fail)
.mockResolvedValueOnce({
exitCode: 1,
stdout: '',
stderr: 'fatal: No such remote',
} as MockRunResult)
// 2. git remote get-url origin
.mockResolvedValueOnce({
exitCode: 0,
stdout: 'git@github.com:owner/repo.git\n',
stderr: '',
} as MockRunResult)
// 3. gh pr view
// 1. gh pr view (using cached data, no git commands needed)
.mockResolvedValueOnce({
exitCode: 0,
stdout: JSON.stringify({ number: 123, url: 'https://github.com/owner/repo/pull/123', state: 'OPEN', isDraft: false }),
Expand All @@ -114,16 +102,16 @@ describe('Git IPC Handlers - Repo Info Cache', () => {
data: { number: 123, url: 'https://github.com/owner/repo/pull/123', state: 'open' },
});

// Verify cache was used - should NOT call git branch or fetch repo info
expect(mockGitExecutor.run).toHaveBeenCalledTimes(3);
// Verify cache was used - should only call gh pr view, not git commands
expect(mockGitExecutor.run).toHaveBeenCalledTimes(1);
// Verify updateSession was not called (cache hit)
expect(mockSessionManager.db.updateSession).not.toHaveBeenCalled();
});

it('should cache repo info on first call', async () => {
// Cache miss - will call fetchAndCacheRepoInfo
mockGitExecutor.run
// 1. git branch --show-current
// 1. git branch --show-current (from fetchAndCacheRepoInfo)
.mockResolvedValueOnce({
exitCode: 0,
stdout: 'feature-branch\n',
Expand All @@ -141,19 +129,7 @@ describe('Git IPC Handlers - Repo Info Cache', () => {
stdout: '',
stderr: 'fatal: No such remote',
} as MockRunResult)
// 4. git remote get-url upstream (from PR search loop)
.mockResolvedValueOnce({
exitCode: 1,
stdout: '',
stderr: 'fatal: No such remote',
} as MockRunResult)
// 5. git remote get-url origin (from PR search loop)
.mockResolvedValueOnce({
exitCode: 0,
stdout: 'git@github.com:owner/repo.git\n',
stderr: '',
} as MockRunResult)
// 6. gh pr view
// 4. gh pr view (using newly cached data)
.mockResolvedValueOnce({
exitCode: 0,
stdout: JSON.stringify({ number: 123, url: 'https://github.com/owner/repo/pull/123', state: 'OPEN', isDraft: false }),
Expand All @@ -177,7 +153,7 @@ describe('Git IPC Handlers - Repo Info Cache', () => {
});

it('should use cached data for fork workflow', async () => {
// Set up cache with fork info
// Set up cache with fork info - need BOTH branch AND owner_repo
mockSessionManager.db.getSession.mockReturnValue({
current_branch: 'feature-branch',
owner_repo: 'upstream-owner/repo',
Expand All @@ -186,13 +162,7 @@ describe('Git IPC Handlers - Repo Info Cache', () => {
});

mockGitExecutor.run
// 1. git remote get-url upstream (will succeed for fork)
.mockResolvedValueOnce({
exitCode: 0,
stdout: 'git@github.com:upstream-owner/repo.git\n',
stderr: '',
} as MockRunResult)
// 2. gh pr view (with fork-owner:branch format)
// 1. gh pr view (with fork-owner:branch format, using cached data)
.mockResolvedValueOnce({
exitCode: 0,
stdout: JSON.stringify({ number: 456, url: 'https://github.com/upstream-owner/repo/pull/456', state: 'OPEN', isDraft: false }),
Expand All @@ -207,7 +177,7 @@ describe('Git IPC Handlers - Repo Info Cache', () => {
});

// Verify the gh command used the fork-owner:branch format
const ghCall = mockGitExecutor.run.mock.calls[1];
const ghCall = mockGitExecutor.run.mock.calls[0];
expect(ghCall[0].argv).toContain('fork-owner:feature-branch');
});
});
Expand Down
Loading
Loading