Skip to content
Closed
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
35 changes: 35 additions & 0 deletions packages/cli/src/commands/workflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,41 @@ describe('workflowRunCommand', () => {
expect(createCallsAfter).toBe(createCallsBefore);
});

it('surfaces auto-registration failures instead of claiming the repo is invalid', async () => {
const { discoverWorkflowsWithConfig } = await import('@archon/workflows/workflow-discovery');
const { registerRepository } = await import('@archon/core');
const conversationDb = await import('@archon/core/db/conversations');
const codebaseDb = await import('@archon/core/db/codebases');
const gitModule = await import('@archon/git');

(discoverWorkflowsWithConfig as ReturnType<typeof mock>).mockResolvedValueOnce({
workflows: [makeTestWorkflowWithSource({ name: 'assist', description: 'Help' })],
errors: [],
});
(conversationDb.getOrCreateConversation as ReturnType<typeof mock>).mockResolvedValueOnce({
id: 'conv-123',
});
(codebaseDb.findCodebaseByDefaultCwd as ReturnType<typeof mock>).mockResolvedValueOnce(null);
(gitModule.findRepoRoot as ReturnType<typeof mock>).mockResolvedValueOnce('/test/path');
(registerRepository as ReturnType<typeof mock>).mockRejectedValueOnce(
new Error(
'Source symlink at /home/test/.archon/workspaces/acme/widget/source already points to ' +
'/home/test/.archon/workspaces/widget, expected /test/path'
)
);

const error = await workflowRunCommand('/test/path', 'assist', 'hello', {}).catch(
err => err as Error
);

expect(error).toBeInstanceOf(Error);
expect(error.message).toContain('Cannot create worktree: repository registration failed.');
expect(error.message).toContain(
'Remove the stale workspace entry at /home/test/.archon/workspaces/acme/widget and retry'
);
expect(error.message).not.toContain('not in a git repository');
});

it('throws when isolation cannot be created due to missing codebase', async () => {
const { discoverWorkflowsWithConfig } = await import('@archon/workflows/workflow-discovery');
const conversationDb = await import('@archon/core/db/conversations');
Expand Down
34 changes: 34 additions & 0 deletions packages/cli/src/commands/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
} from '@archon/workflows/event-emitter';
import type { WorkflowLoadResult } from '@archon/workflows/schemas/workflow';
import type { WorkflowRun } from '@archon/workflows/schemas/workflow-run';
import { join } from 'node:path';
import {
approveWorkflow,
rejectWorkflow,
Expand Down Expand Up @@ -77,6 +78,31 @@ function generateConversationId(): string {
return `cli-${String(timestamp)}-${random}`;
}

function extractStaleWorkspaceEntry(message: string): string | null {
const prefix = 'Source symlink at ';
const delimiter = ' already points to ';
if (!message.startsWith(prefix)) return null;

const remainder = message.slice(prefix.length);
const delimiterIndex = remainder.indexOf(delimiter);
if (delimiterIndex === -1) return null;

const sourcePath = remainder.slice(0, delimiterIndex).trim();
const lastSeparator = Math.max(sourcePath.lastIndexOf('/'), sourcePath.lastIndexOf('\\'));
return lastSeparator === -1 ? null : sourcePath.slice(0, lastSeparator);
}

function buildRegistrationFailureError(action: string, error: Error): Error {
const staleWorkspaceEntry = extractStaleWorkspaceEntry(error.message);
const hint = staleWorkspaceEntry
? `Hint: Remove the stale workspace entry at ${staleWorkspaceEntry} and retry, or use --no-worktree to skip isolation.`
: `Hint: Check your Archon workspace registration under ${join(getArchonHome(), 'workspaces')} and retry, or use --no-worktree to skip isolation.`;

return new Error(
`Cannot ${action}: repository registration failed.\n` + `Error: ${error.message}\n` + hint
);
}

/** Render a workflow event to stderr as a progress line. Called only when --quiet is not set. */
function renderWorkflowEvent(event: WorkflowEmitterEvent, verbose: boolean): void {
switch (event.type) {
Expand Down Expand Up @@ -285,6 +311,7 @@ export async function workflowRunCommand(
// Try to find a codebase for this directory
let codebase = null;
let codebaseLookupError: Error | null = null;
let codebaseRegistrationError: Error | null = null;
try {
codebase = await codebaseDb.findCodebaseByDefaultCwd(cwd);
} catch (error) {
Expand Down Expand Up @@ -330,6 +357,7 @@ export async function workflowRunCommand(
}
} catch (error) {
const err = error as Error;
codebaseRegistrationError = err;
getLog().warn(
{ err, errorType: err.constructor.name, repoRoot },
'cli.codebase_auto_registration_failed'
Expand All @@ -354,6 +382,9 @@ export async function workflowRunCommand(
'Hint: Check your database connection before using --resume.'
);
}
if (codebaseRegistrationError) {
throw buildRegistrationFailureError('resume', codebaseRegistrationError);
}
throw new Error(
'Cannot resume: Not in a git repository.\n' +
'Either run from a git repo or use /clone first.'
Expand Down Expand Up @@ -507,6 +538,9 @@ export async function workflowRunCommand(
'Hint: Check your database connection, or use --no-worktree to skip isolation.'
);
}
if (codebaseRegistrationError) {
throw buildRegistrationFailureError('create worktree', codebaseRegistrationError);
}
throw new Error(
'Cannot create worktree: not in a git repository.\n' +
'Run from within a git repo, or use --no-worktree to skip isolation.'
Expand Down