{"record":{"id":"65c089c4b0dbb83f","repo":"deepseek-ai/deepseek-harness","slug":"command-directory-warmup-failed-entry-lasterror","errorCode":null,"errorMessage":"command directory warmup failed: ${entry.lastError instanceof Error ? entry.lastError.message : String(entry.lastError)}","messagePattern":"command directory warmup failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/client/ui-commands/src/client/directory.ts","lineNumber":130,"sourceCode":"  }\n\n  /**\n   * Strong-wait until one session's catalog is servable (the enter-\n   * adjudication \"directory must be reached\" rule): ready returns at once;\n   * cold/failed launch a fresh pull; pending joins the flying one. Rejects\n   * when the awaited pull fails or the signal aborts.\n   * @param sessionId - session key.\n   * @param signal - attempt-scoped abort (the SubmitAttempt signal).\n   * @returns the hot command snapshot.\n   */\n  async ensureReady(sessionId: SessionId, signal: AbortSignal): Promise<readonly CommandDescriptor[]> {\n    const entry = this.entry(sessionId)\n    while (true) {\n      if (entry.state === 'ready') return entry.commands\n      if (entry.state !== 'pending') void this.refresh(sessionId)\n      await settled(entry, signal)\n      if (entry.state === 'failed') {\n        throw new Error(`command directory warmup failed: ${entry.lastError instanceof Error ? entry.lastError.message : String(entry.lastError)}`)\n      }\n      // Still pending (the awaited pull was superseded) → wait for the winner.\n    }\n  }\n\n  private entry(sessionId: SessionId): Entry {\n    let entry = this.entries.get(sessionId)\n    if (entry === undefined) {\n      entry = new Entry()\n      this.entries.set(sessionId, entry)\n    }\n    return entry\n  }\n}\n\n/** One settlement tick for one entry: resolves at the next winning publish, rejects on abort. */\nfunction settled(entry: Entry, signal: AbortSignal): Promise<void> {\n  if (signal.aborted) return Promise.reject(abortReason(signal))","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/deepseek-ai/deepseek-harness/blob/b150a551b8d465e31e418e1b2eaf5e79bbb7d28e/packages/client/ui-commands/src/client/directory.ts#L112-L148","documentation":"The session-keyed CommandDirectory caches each session's slash-command catalog. ensureReady() strong-waits until a servable snapshot exists (Enter adjudication requires the directory to be reached): cold/failed entries launch refresh(), pending entries join the in-flight pull. When the winning pull rejects, its stored entry.lastError is rethrown wrapped as 'command directory warmup failed: ...' — the inner text is almost always the underlying command.list RPC error.","triggerScenarios":"Pressing Enter on a slash-command line (matchEnter) or calling list() while the session's cache entry is in state 'failed': the latest refresh() pull (the epoch winner) rejected because the command.list RPC returned an error, the transport dropped, or the attempt's AbortSignal fired while waiting on settled(entry, signal).","commonSituations":"Web client loses the host connection (server restart, network drop, standby/resume) and the user hits Enter on a slash command before a successful reconnect; the host rejects command.list for a session it already closed; first paint where the scope-birth warm hook races a dying connection.","solutions":["Restore the host connection and retry Enter — resetConnected() drops stale snapshots and prewarms every key on reconnect","Read the inner message (the wrapped entry.lastError) to get the concrete command.list error code and act on that instead of the wrapper","Confirm the session is alive and the host serves command.list for this sessionId (subagent-addressed sessions short-circuit to an empty list without any RPC)","In UI code, catch this error and offer a retry affordance instead of surfacing a raw rejection"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"if (directory.status(sessionId) !== 'ready') {\n  await directory.refresh(sessionId) // start a fresh epoch before strong-waiting\n}\nconst commands = await directory.ensureReady(sessionId, signal)","typeGuard":null,"tryCatchPattern":"try {\n  return await directory.ensureReady(sessionId, signal)\n} catch (error) {\n  if (signal.aborted) throw error\n  await directory.refresh(sessionId) // a new epoch clears the failed state\n  return directory.ensureReady(sessionId, signal)\n}","preventionTips":["Prewarm with warm() when a session scope mounts so Enter rarely waits on a cold pull","Call resetConnected() on reconnect so failed entries cannot serve stale rejections","Diagnose the inner lastError message, not the wrapper text"],"tags":["rpc","cache-warmup","slash-commands","reconnect"],"backgroundTag":"rpc-request-failed","analyzedSha":"b150a551b8d465e31e418e1b2eaf5e79bbb7d28e","analyzedAt":"2026-08-24T18:12:29.105Z","schemaVersion":2},"datasetVersion":"2026-08-24T22:17:12.610Z"}