diff --git a/packages/core/src/evaluation/results-repo.ts b/packages/core/src/evaluation/results-repo.ts index 53cfcb8e8..3f1d070b9 100644 --- a/packages/core/src/evaluation/results-repo.ts +++ b/packages/core/src/evaluation/results-repo.ts @@ -252,11 +252,27 @@ export function normalizeResultsConfig( }; } +// GitHub `owner/repo` shorthand: exactly two non-empty path segments and no +// scheme, host, or extra slashes. A bare local path like `.` does not match +// (no `/`), so it is never expanded into a clone URL. +const GITHUB_OWNER_REPO_SHORTHAND = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/; + +function isExplicitRemoteUrl(value: string): boolean { + return value.includes('://') || value.startsWith('git@'); +} + export function resolveResultsRepoUrl(repo: string): string { - if (repo.includes('://') || repo.startsWith('git@')) { - return repo; + const trimmed = repo.trim(); + if (isExplicitRemoteUrl(trimmed)) { + return trimmed; } - return `https://github.com/${repo}.git`; + // Only genuine GitHub `owner/repo` shorthand is expanded to a clone URL. + // Local paths (including `.`) and other non-URL values are returned unchanged + // so we never synthesize a bogus URL such as `https://github.com/..git`. + if (GITHUB_OWNER_REPO_SHORTHAND.test(trimmed)) { + return `https://github.com/${trimmed}.git`; + } + return trimmed; } export function getResultsRepoLocalPaths(repo: string): ResultsRepoLocalPaths { @@ -606,6 +622,13 @@ async function ensureResultsRepoRemote( } const remoteUrl = resolveResultsRepoUrl(config.repo_url); + // Same-repo results push to the project's existing remote (typically + // `origin`). Never rewrite that remote to a value that is not a real Git + // remote URL (e.g. a local results path or `.`), which would otherwise + // clobber a correct origin with a synthesized URL. + if (!isExplicitRemoteUrl(remoteUrl)) { + return; + } const { stdout } = await runGit(['remote', 'get-url', config.remote], { cwd: repoDir, check: false, diff --git a/packages/core/test/evaluation/results-repo.test.ts b/packages/core/test/evaluation/results-repo.test.ts index 285b53408..fd98efc4b 100644 --- a/packages/core/test/evaluation/results-repo.test.ts +++ b/packages/core/test/evaluation/results-repo.test.ts @@ -29,6 +29,7 @@ import { normalizeResultsConfig, pushWipCheckpoint, readGitResultArtifact, + resolveResultsRepoUrl, setupWipWorktree, syncResultsRepo, syncResultsRepoForProject, @@ -532,6 +533,32 @@ describe('listGitRuns', () => { }); }); +describe('resolveResultsRepoUrl', () => { + it('expands genuine GitHub owner/repo shorthand to a clone URL', () => { + expect(resolveResultsRepoUrl('EntityProcess/agentv')).toBe( + 'https://github.com/EntityProcess/agentv.git', + ); + }); + + it('returns explicit remote URLs unchanged', () => { + expect(resolveResultsRepoUrl('https://github.com/example/source.git')).toBe( + 'https://github.com/example/source.git', + ); + expect(resolveResultsRepoUrl('git@github.com:example/source.git')).toBe( + 'git@github.com:example/source.git', + ); + expect(resolveResultsRepoUrl('file:///tmp/results.git')).toBe('file:///tmp/results.git'); + }); + + it('never synthesizes a GitHub URL from a local path like "."', () => { + // Regression: same-repo results (`repo_path: .`) previously produced the + // malformed remote `https://github.com/..git` and overrode the project's + // correct origin. A local path must be returned unchanged. + expect(resolveResultsRepoUrl('.')).toBe('.'); + expect(resolveResultsRepoUrl('/data/repos/WTG.AI.Prompts')).toBe('/data/repos/WTG.AI.Prompts'); + }); +}); + describe('results repo write path', () => { let rootDir: string;