Skip to content
Open
Changes from 1 commit
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
38 changes: 26 additions & 12 deletions apps/cli/src/backends/claude/sdk/metadataExtractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,27 @@ export interface SDKMetadata {
slashCommands?: string[]
}

/** Maximum time to wait for SDK metadata extraction before giving up. */
const METADATA_EXTRACTION_TIMEOUT_MS = 10_000
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Make metadata timeout configurable instead of hardcoded.

METADATA_EXTRACTION_TIMEOUT_MS is hardcoded. Please source it from environment/config (with a safe default) so operators can tune behavior per environment.

Suggested change
-/** Maximum time to wait for SDK metadata extraction before giving up. */
-const METADATA_EXTRACTION_TIMEOUT_MS = 10_000
+/** Maximum time to wait for SDK metadata extraction before giving up. */
+const DEFAULT_METADATA_EXTRACTION_TIMEOUT_MS = 10_000
+const METADATA_EXTRACTION_TIMEOUT_MS = (() => {
+    const raw = process.env.HAPPIER_METADATA_EXTRACTION_TIMEOUT_MS
+    const parsed = raw ? Number.parseInt(raw, 10) : NaN
+    return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_METADATA_EXTRACTION_TIMEOUT_MS
+})()

As per coding guidelines: "Avoid hardcoded behavior - load configuration from YAML/environment variables instead".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/cli/src/backends/claude/sdk/metadataExtractor.ts` around lines 15 - 16,
The constant METADATA_EXTRACTION_TIMEOUT_MS is hardcoded; change it to be
configurable by reading from environment/config with a safe default of 10000ms
(e.g., use process.env.METADATA_EXTRACTION_TIMEOUT_MS or your project's config
loader to parse an integer and fall back to 10_000). Replace the current const
with code that reads and validates the configured value (coerce to number,
handle NaN) and keep the same exported/used symbol name
METADATA_EXTRACTION_TIMEOUT_MS so existing callers in metadataExtractor.ts
continue to work.


/**
* Extract SDK metadata by running a minimal query and capturing the init message
* Extract SDK metadata by running a minimal query and capturing the init message.
*
* Times out after METADATA_EXTRACTION_TIMEOUT_MS to prevent indefinite hangs
* when the spawned SDK process blocks (e.g. on slow or inaccessible filesystems).
*
* @returns SDK metadata containing tools and slash commands
*/
export async function extractSDKMetadata(): Promise<SDKMetadata> {
const abortController = new AbortController()

const timeoutId = setTimeout(() => {
logger.debug(`[metadataExtractor] Extraction timed out after ${METADATA_EXTRACTION_TIMEOUT_MS}ms`)
abortController.abort()
}, METADATA_EXTRACTION_TIMEOUT_MS)

try {
logger.debug('[metadataExtractor] Starting SDK metadata extraction')

// Run SDK with minimal tools allowed
const sdkQuery = query({
prompt: 'hello',
Expand All @@ -36,28 +47,31 @@ export async function extractSDKMetadata(): Promise<SDKMetadata> {
for await (const message of sdkQuery) {
if (message.type === 'system' && message.subtype === 'init') {
const systemMessage = message as SDKSystemMessage

const metadata: SDKMetadata = {
tools: systemMessage.tools,
slashCommands: systemMessage.slash_commands
}

logger.debug('[metadataExtractor] Captured SDK metadata:', metadata)

// Abort the query since we got what we need
clearTimeout(timeoutId)
abortController.abort()

return metadata
}
}


clearTimeout(timeoutId)
logger.debug('[metadataExtractor] No init message received from SDK')
return {}

} catch (error) {
// Check if it's an abort error (expected)
clearTimeout(timeoutId)
// Check if it's an abort error (expected — either from timeout or after capture)
if (error instanceof Error && error.name === 'AbortError') {
logger.debug('[metadataExtractor] SDK query aborted after capturing metadata')
logger.debug('[metadataExtractor] SDK query aborted (timeout or after capturing metadata)')
return {}
}
logger.debug('[metadataExtractor] Error extracting SDK metadata:', error)
Expand All @@ -79,4 +93,4 @@ export function extractSDKMetadataAsync(onComplete: (metadata: SDKMetadata) => v
.catch(error => {
logger.debug('[metadataExtractor] Async extraction failed:', error)
})
}
}