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
23 changes: 23 additions & 0 deletions packages/happy-cli/src/claude/claudeLocal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,29 @@ describe('claudeLocal --continue handling', () => {
expect(mockSandboxCleanup).toHaveBeenCalledTimes(1);
});

it('should place claudeArgs after --settings so positional prompts are last (regression #663)', async () => {
mockClaudeFindLastSession.mockReturnValue(null);

await claudeLocal({
abort: new AbortController().signal,
sessionId: null,
path: '/tmp',
onSessionFound,
claudeArgs: ['/review https://example.com/pr/123'],
hookSettingsPath: '/tmp/hook-settings.json',
});

const spawnArgs: string[] = mockSpawn.mock.calls[0][1];

const settingsIdx = spawnArgs.indexOf('--settings');
const promptIdx = spawnArgs.indexOf('/review https://example.com/pr/123');

expect(settingsIdx).toBeGreaterThan(-1);
expect(promptIdx).toBeGreaterThan(-1);
// Positional prompt must come AFTER --settings and its value
expect(promptIdx).toBeGreaterThan(settingsIdx + 1);
});

it('should continue without sandbox when initialization fails', async () => {
mockInitializeSandbox.mockRejectedValue(new Error('sandbox failed'));

Expand Down
11 changes: 6 additions & 5 deletions packages/happy-cli/src/claude/claudeLocal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,17 +219,18 @@ export async function claudeLocal(opts: {
args.push('--allowedTools', opts.allowedTools.join(','));
}

// Add custom Claude arguments
if (opts.claudeArgs) {
args.push(...opts.claudeArgs)
}

// Add hook settings for session tracking (when available)
if (opts.hookSettingsPath) {
args.push('--settings', opts.hookSettingsPath);
logger.debug(`[ClaudeLocal] Using hook settings: ${opts.hookSettingsPath}`);
}

// Add custom Claude arguments last so positional prompts
// (e.g. happy "/review URL") end up at the end of the argv
if (opts.claudeArgs) {
args.push(...opts.claudeArgs)
}

if (!claudeCliPath || !existsSync(claudeCliPath)) {
throw new Error('Claude local launcher not found. Please ensure HAPPY_PROJECT_ROOT is set correctly for development.');
}
Expand Down
9 changes: 4 additions & 5 deletions packages/happy-cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -603,12 +603,11 @@ ${chalk.bold('To clean up runaway processes:')} Use ${chalk.cyan('happy doctor c
console.warn(chalk.yellow(` To configure Claude, edit ~/.claude/settings.json instead.`))
// Don't pass through to claudeArgs
} else {
// Pass unknown arguments through to claude
// Pass unknown arguments through to claude as-is.
// Don't try to pair with the next arg — the shell already
// handles quoting, and the heuristic breaks positional
// arguments like: happy "/review URL"
unknownArgs.push(arg)
// Check if this arg expects a value (simplified check for common patterns)
if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
unknownArgs.push(args[++i])
}
}
}

Expand Down