Skip to content

Commit c20e887

Browse files
committed
Don't flatten SSH failures into empty results
Remote methods (readRemoteFile, listSessionIds, getRemoteDirectorySize) now distinguish expected not-found/permission errors from unexpected SSH transport/auth failures. Unexpected failures are logged with logger.warn and reported to Sentry via captureException, preventing silent degradation where auth regressions appear as 'no sessions'.
1 parent 1a07e77 commit c20e887

1 file changed

Lines changed: 43 additions & 4 deletions

File tree

src/main/storage/copilot-session-storage.ts

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -451,12 +451,35 @@ export class CopilotSessionStorage extends BaseSessionStorage {
451451
}
452452
}
453453

454+
/** Check if a remote-fs error indicates a benign not-found/permission case vs an unexpected SSH failure. */
455+
private isExpectedRemoteError(error?: string): boolean {
456+
if (!error) return false;
457+
const lower = error.toLowerCase();
458+
return (
459+
lower.includes('not found') ||
460+
lower.includes('not accessible') ||
461+
lower.includes('no such file') ||
462+
lower.includes('permission denied') ||
463+
lower.includes('does not exist')
464+
);
465+
}
466+
454467
/** List all session directory names from the session state directory. */
455468
private async listSessionIds(sshConfig?: SshRemoteConfig): Promise<string[]> {
456469
const sessionStateDir = this.getSessionStateDir(sshConfig);
457470
if (sshConfig) {
458471
const result = await readDirRemote(sessionStateDir, sshConfig);
459472
if (!result.success || !result.data) {
473+
if (!this.isExpectedRemoteError(result.error)) {
474+
logger.warn(
475+
`Unexpected SSH failure listing Copilot sessions: ${result.error}`,
476+
LOG_CONTEXT
477+
);
478+
captureException(new Error(result.error || 'readDirRemote failed'), {
479+
operation: 'copilotStorage:listSessionIds:remote',
480+
sessionStateDir,
481+
});
482+
}
460483
return [];
461484
}
462485
return result.data.filter((entry) => entry.isDirectory).map((entry) => entry.name);
@@ -590,21 +613,37 @@ export class CopilotSessionStorage extends BaseSessionStorage {
590613
}
591614
}
592615

593-
/** Read a file from a remote host via SSH. Returns null on failure. */
616+
/** Read a file from a remote host via SSH. Returns null on not-found; reports unexpected failures to Sentry. */
594617
private async readRemoteFile(
595618
filePath: string,
596619
sshConfig: SshRemoteConfig
597620
): Promise<string | null> {
598621
const result = await readFileRemote(filePath, sshConfig);
599-
return result.success && result.data ? result.data : null;
622+
if (result.success && result.data) return result.data;
623+
if (!this.isExpectedRemoteError(result.error)) {
624+
logger.warn(`Unexpected SSH failure reading ${filePath}: ${result.error}`, LOG_CONTEXT);
625+
captureException(new Error(result.error || 'readFileRemote failed'), {
626+
operation: 'copilotStorage:readRemoteFile',
627+
filePath,
628+
});
629+
}
630+
return null;
600631
}
601632

602-
/** Calculate the total size of a session directory on a remote host. */
633+
/** Calculate the total size of a session directory on a remote host. Returns 0 on not-found; reports unexpected failures. */
603634
private async getRemoteDirectorySize(
604635
sessionDir: string,
605636
sshConfig: SshRemoteConfig
606637
): Promise<number> {
607638
const result = await directorySizeRemote(sessionDir, sshConfig);
608-
return result.success && result.data ? result.data : 0;
639+
if (result.success && result.data) return result.data;
640+
if (!this.isExpectedRemoteError(result.error)) {
641+
logger.warn(`Unexpected SSH failure sizing ${sessionDir}: ${result.error}`, LOG_CONTEXT);
642+
captureException(new Error(result.error || 'directorySizeRemote failed'), {
643+
operation: 'copilotStorage:getRemoteDirectorySize',
644+
sessionDir,
645+
});
646+
}
647+
return 0;
609648
}
610649
}

0 commit comments

Comments
 (0)