Summary
In withRepositorySource(), a failure to delete the temporary checkout throws even when the analysis succeeded, so a complete report is discarded and the CLI exits 1.
packages/cli/src/repository-source.ts:
} catch (error) {
primaryError = error;
throw error;
} finally {
try {
await removeTemporaryDirectory(temporaryRoot);
} catch (cleanupError) {
const cleanupMessage = `Could not remove temporary checkout "${temporaryRoot}": ...`;
if (primaryError instanceof Error) {
throw new RepositorySourceError(`${primaryError.message} ${cleanupMessage}`);
}
throw new RepositorySourceError(cleanupMessage); // <- success path
}
}
A throw inside finally replaces the pending return value. When work() returned a valid FixMapReport, that report is thrown away and the user sees only a cleanup error.
Why this matters in practice
The temporary directory is a freshly cloned git repo. Removing one is failure-prone on Windows — .git/objects pack files are written read-only, and Defender / Search Indexer routinely hold transient handles, yielding EPERM / EBUSY. The maxRetries: 3, retryDelay: 100 on rm reduces the window but does not close it.
So on Windows, fixmap plan --issue "..." --repo https://github.com/owner/repo can do all the work correctly and still fail with an unrelated cleanup message.
Secondary problem
When both the work and the cleanup fail, the original error is flattened into a string and rethrown as a RepositorySourceError. The original error type, cause, and stack are lost, so callers can no longer distinguish e.g. a clone failure from a scan failure.
Suggested fix
Treat cleanup as best-effort:
- If
work() succeeded, return the report and surface the cleanup failure as a ScanDiagnostic (severity: "warning") appended to report.diagnostics, mentioning the leftover path so the user can delete it.
- If
work() failed, rethrow the original error, attaching the cleanup failure via cause rather than string concatenation.
Summary
In
withRepositorySource(), a failure to delete the temporary checkout throws even when the analysis succeeded, so a complete report is discarded and the CLI exits 1.packages/cli/src/repository-source.ts:A
throwinsidefinallyreplaces the pending return value. Whenwork()returned a validFixMapReport, that report is thrown away and the user sees only a cleanup error.Why this matters in practice
The temporary directory is a freshly cloned git repo. Removing one is failure-prone on Windows —
.git/objectspack files are written read-only, and Defender / Search Indexer routinely hold transient handles, yieldingEPERM/EBUSY. ThemaxRetries: 3, retryDelay: 100onrmreduces the window but does not close it.So on Windows,
fixmap plan --issue "..." --repo https://github.com/owner/repocan do all the work correctly and still fail with an unrelated cleanup message.Secondary problem
When both the work and the cleanup fail, the original error is flattened into a string and rethrown as a
RepositorySourceError. The original error type,cause, and stack are lost, so callers can no longer distinguish e.g. a clone failure from a scan failure.Suggested fix
Treat cleanup as best-effort:
work()succeeded, return the report and surface the cleanup failure as aScanDiagnostic(severity: "warning") appended toreport.diagnostics, mentioning the leftover path so the user can delete it.work()failed, rethrow the original error, attaching the cleanup failure viacauserather than string concatenation.