Skip to content
Merged
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
529 changes: 0 additions & 529 deletions BEADS_AUDIT_REPORT.md

This file was deleted.

12 changes: 12 additions & 0 deletions apps/server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import dotenv from 'dotenv';
import { createEventEmitter, type EventEmitter } from './lib/events.js';
import { initAllowedPaths } from '@automaker/platform';
import { authMiddleware, initializeAuth } from './lib/auth.js';
import { setAuthConfig } from './lib/claude-auth-manager.js';
import { apiLimiter, healthLimiter, beadsLimiter, strictLimiter } from './lib/rate-limiter.js';
import { createFsRoutes } from './routes/fs/index.js';
import { createHealthRoutes } from './routes/health/index.js';
Expand Down Expand Up @@ -171,6 +172,17 @@ const beadsService = new BeadsService();
(async () => {
await agentService.initialize();
console.log('[Server] Agent service initialized');

// Load Claude authentication configuration from settings
try {
const globalSettings = await settingsService.getGlobalSettings();
const authMethod = globalSettings.claudeAuthMethod || 'auto';
setAuthConfig({ method: authMethod });
console.log(`[Server] Claude auth config loaded: ${authMethod}`);
} catch (error) {
console.warn('[Server] Failed to load auth config, using default:', error);
setAuthConfig({ method: 'auto' });
}
})();

// ============================================================================
Expand Down
1 change: 0 additions & 1 deletion apps/server/src/lib/beads-validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ export const createBeadsIssueSchema = z.object({
type: beadsIssueTypeSchema.optional(),
priority: beadsIssuePrioritySchema.optional(),
labels: beadsLabelsSchema,
Comment on lines 69 to 71

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve parentIssueId in create-issue validation

The create issue validator now only accepts title/description/type/priority/labels. That means any client that sends parentIssueId (which CreateBeadsIssueInput and BeadsService.createIssue still support for --parent) will now get a 400 validation error, so creating subtasks/child issues via the API is silently broken. This is a regression for any UI or API consumer that relies on hierarchical Beads issues.

Useful? React with 👍 / 👎.

parentIssueId: beadsIssueIdSchema.optional(),
});

/**
Expand Down
41 changes: 37 additions & 4 deletions apps/server/src/providers/claude-provider.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/**
* Claude Provider - Executes queries using Claude Agent SDK
* Claude Provider - Executes queries using Claude Agent SDK or CLI
*
* Wraps the @anthropic-ai/claude-agent-sdk for seamless integration
* with the provider architecture.
* Routes through unified client when CLI auth is configured.
* Falls back to SDK for API key auth.
*/

import { query, type Options, type SDKUserMessage } from '@anthropic-ai/claude-agent-sdk';
Expand All @@ -14,14 +14,17 @@ import type {
ModelDefinition,
ContentBlock,
} from './types.js';
import { getAuthStatus } from '../lib/claude-auth-manager.js';

export class ClaudeProvider extends BaseProvider {
getName(): string {
return 'claude';
}

/**
* Execute a query using Claude Agent SDK
* Execute a query using Claude Agent SDK or CLI (if configured)
*
* Routes through unified client when CLI auth is configured.
*/
async *executeQuery(options: ExecuteOptions): AsyncGenerator<ProviderMessage> {
const {
Expand All @@ -36,6 +39,36 @@ export class ClaudeProvider extends BaseProvider {
sdkSessionId,
} = options;

// Check if we should use CLI auth
const authStatus = await getAuthStatus();
const useCLI =
authStatus.method === 'cli' ||
(authStatus.method === 'auto' &&
authStatus.cli?.installed &&
Comment on lines +43 to +47

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Do not fall back to API key in CLI-only mode

In CLI-only mode, getAuthStatus() leaves method undefined when the CLI is missing or unauthenticated. This logic therefore sets useCLI to false and falls back to the SDK path, which will use an API key if present. The result is that a user who explicitly selected cli can still run requests against the API key when the CLI isn’t ready, which defeats the “CLI-only” preference and can cause unexpected billing/behavior instead of surfacing the intended auth error.

Useful? React with 👍 / 👎.

authStatus.cli?.authenticated);

if (useCLI) {
// Use unified client for CLI mode
console.log('[ClaudeProvider] Using CLI authentication');
const { executeUnifiedQuery } = await import('../lib/unified-claude-client.js');
yield* executeUnifiedQuery({
prompt,
model,
cwd,
systemPrompt,
maxTurns,
allowedTools,
abortController,
conversationHistory,
sdkSessionId,
forceAuthMethod: 'cli',
});
return;
}

// Use SDK for API key mode (original behavior)
console.log('[ClaudeProvider] Using API key authentication');

// Build Claude SDK options
const defaultTools = ['Read', 'Write', 'Edit', 'Glob', 'Grep', 'Bash', 'WebSearch', 'WebFetch'];
const toolsToUse = allowedTools || defaultTools;
Expand Down
8 changes: 8 additions & 0 deletions apps/server/src/routes/settings/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import { createGetProjectHandler } from './routes/get-project.js';
import { createUpdateProjectHandler } from './routes/update-project.js';
import { createMigrateHandler } from './routes/migrate.js';
import { createStatusHandler } from './routes/status.js';
import { createAuthStatusHandler } from './routes/auth-status.js';
import { createUpdateAuthMethodHandler } from './routes/update-auth-method.js';

/**
* Create settings router with all endpoints
Expand All @@ -36,6 +38,8 @@ import { createStatusHandler } from './routes/status.js';
* - PUT /global - Update global settings
* - GET /credentials - Get masked credentials (safe for UI)
* - PUT /credentials - Update API keys
* - GET /auth - Get Claude authentication status
* - PUT /auth - Update Claude authentication method
* - POST /project - Get project settings (requires projectPath in body)
* - PUT /project - Update project settings
* - POST /migrate - Migrate settings from localStorage
Expand All @@ -57,6 +61,10 @@ export function createSettingsRoutes(settingsService: SettingsService): Router {
router.get('/credentials', createGetCredentialsHandler(settingsService));
router.put('/credentials', createUpdateCredentialsHandler(settingsService));

// Claude authentication configuration
router.get('/auth', createAuthStatusHandler(settingsService));
router.put('/auth', createUpdateAuthMethodHandler(settingsService));

// Project settings
router.post(
'/project',
Expand Down
36 changes: 36 additions & 0 deletions apps/server/src/routes/settings/routes/auth-status.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* GET /api/settings/auth - Get Claude authentication status
*
* Returns current auth configuration including:
* - Configured auth method (api_key, cli, auto)
* - CLI detection status
* - API key status
* - Effective auth method (what will actually be used)
*/

import type { Request, Response } from 'express';
import type { SettingsService } from '../../../services/settings-service.js';
import { getAuthStatus } from '../../../lib/claude-auth-manager.js';

export function createAuthStatusHandler(settingsService: SettingsService) {
return async (req: Request, res: Response): Promise<void> => {
try {
const authStatus = await getAuthStatus();
const globalSettings = await settingsService.getGlobalSettings();

res.json({
success: true,
config: {
method: globalSettings.claudeAuthMethod || 'auto',
},
status: authStatus,
});
} catch (error) {
console.error('[Settings] Auth status error:', error);
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'Failed to get auth status',
});
}
};
}
58 changes: 58 additions & 0 deletions apps/server/src/routes/settings/routes/update-auth-method.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* PUT /api/settings/auth - Update Claude authentication method
*
* Updates the preferred authentication method and persists to settings.
*
* Body: { claudeAuthMethod: 'api_key' | 'cli' | 'auto' }
*/

import type { Request, Response } from 'express';
import type { SettingsService } from '../../../services/settings-service.js';
import { setAuthConfig, getAuthStatus } from '../../../lib/claude-auth-manager.js';

export function createUpdateAuthMethodHandler(settingsService: SettingsService) {
return async (req: Request, res: Response): Promise<void> => {
try {
const { claudeAuthMethod } = req.body as {
claudeAuthMethod?: 'api_key' | 'cli' | 'auto';
};

// Validate
if (
!claudeAuthMethod ||
!['api_key', 'cli', 'auto'].includes(claudeAuthMethod)
) {
Comment on lines +16 to +24

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The type assertion on req.body is not fully type-safe at runtime. If req.body is not an object or claudeAuthMethod has an unexpected type, the validation might not behave as expected. A more robust validation approach is to check the type before checking the value. This avoids relying on a type assertion and makes the handler more resilient to malformed requests.

      const claudeAuthMethod = req.body?.claudeAuthMethod;

      // Validate
      if (
        typeof claudeAuthMethod !== 'string' ||
        !['api_key', 'cli', 'auto'].includes(claudeAuthMethod)
      ) {

res.status(400).json({
success: false,
error:
'Invalid auth method. Must be api_key, cli, or auto',
});
return;
}

// Update in-memory config
setAuthConfig({ method: claudeAuthMethod });

// Persist to settings
await settingsService.updateGlobalSettings({ claudeAuthMethod });

// Return updated status
const authStatus = await getAuthStatus();

res.json({
success: true,
config: {
method: claudeAuthMethod,
},
status: authStatus,
});
} catch (error) {
console.error('[Settings] Update auth method error:', error);
res.status(500).json({
success: false,
error:
error instanceof Error ? error.message : 'Failed to update auth method',
});
}
};
}
5 changes: 5 additions & 0 deletions libs/types/src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,10 @@ export interface GlobalSettings {
/** Which model to use for feature name/description enhancement */
enhancementModel: AgentModel;

// Claude Authentication
/** Preferred Claude authentication method (api_key, cli, auto) */
claudeAuthMethod?: 'api_key' | 'cli' | 'auto';

// Input Configuration
/** User's keyboard shortcut bindings */
keyboardShortcuts: KeyboardShortcuts;
Expand Down Expand Up @@ -437,6 +441,7 @@ export const DEFAULT_GLOBAL_SETTINGS: GlobalSettings = {
defaultAIProfileId: null,
muteDoneSound: false,
enhancementModel: 'sonnet',
claudeAuthMethod: 'auto',
keyboardShortcuts: DEFAULT_KEYBOARD_SHORTCUTS,
aiProfiles: [],
projects: [],
Expand Down