Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions packages/core/src/handlers/clone.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,32 @@ describe('registerRepository', () => {
expect(createArg.name).toBe('acme/backend');
});

test('builds owner/repo name from non-GitHub SSH remote URL without colon in name', async () => {
// GitLab / self-hosted SSH URLs: git@host:org/repo — the host must NOT appear in
// the codebase name or worktree path because colons break Java classpaths on Unix.
spyExecFileAsync.mockImplementation((cmd: string, args: string[]) => {
if (args.includes('rev-parse')) return Promise.resolve({ stdout: '.git', stderr: '' });
if (args.includes('get-url'))
return Promise.resolve({
stdout: 'git@gitlab.example.com:myorg/myproject.git',
stderr: '',
});
return Promise.resolve({ stdout: '', stderr: '' });
});
mockFindCodebaseByDefaultCwd.mockResolvedValueOnce(null);
mockCreateCodebase.mockResolvedValueOnce(
makeCodebase({ name: 'myorg/myproject' }) as ReturnType<typeof makeCodebase>
);

await registerRepository('/home/user/myproject');

const createArg = mockCreateCodebase.mock.calls[0]?.[0] as { name: string };
// Must be plain owner/repo — no SSH host, no colon
expect(createArg.name).toBe('myorg/myproject');
expect(createArg.name).not.toContain(':');
expect(createArg.name).not.toContain('@');
});

// ── Command auto-loading ───────────────────────────────────────────────
test('auto-loads markdown commands found in .archon/commands', async () => {
spyExecFileAsync.mockImplementation((cmd: string, args: string[]) => {
Expand Down
24 changes: 14 additions & 10 deletions packages/core/src/handlers/clone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,16 +328,20 @@ export async function registerRepository(localPath: string): Promise<RegisterRes
let ownerName = '_local';
if (remoteUrl) {
const cleaned = remoteUrl.replace(/\.git$/, '').replace(/\/+$/, '');
let workingRemote = cleaned;
if (cleaned.startsWith('git@github.com:')) {
workingRemote = cleaned.replace('git@github.com:', 'https://github.com/');
}
const parts = workingRemote.split('/');
const r = parts.pop();
const o = parts.pop();
if (o && r) {
name = `${o}/${r}`;
ownerName = o;
// Handle any SSH git URL (git@host:owner/repo) by extracting just owner/repo.
// This avoids colons in worktree paths, which break Java classpaths on Unix.
const sshMatch = /^git@[^:]+:([^/]+)\/(.+)$/.exec(cleaned);
if (sshMatch) {
name = `${sshMatch[1]}/${sshMatch[2]}`;
ownerName = sshMatch[1];
} else {
const parts = cleaned.split('/');
const r = parts.pop();
const o = parts.pop();
if (o && r) {
name = `${o}/${r}`;
ownerName = o;
}
}
}

Expand Down
Loading