{"record":{"id":"98089a59a7d6aa8b","repo":"deepseek-ai/deepseek-harness","slug":"ctx-level-renderslot-only-renders-root-got-k","errorCode":null,"errorMessage":"ctx-level renderSlot only renders 'root' (got \"${key}\"); child slots render through the component props face","messagePattern":"ctx-level renderSlot only renders 'root' \\(got \"(.+?)\"\\); child slots render through the component props face","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/client/runtime/src/client/slots.ts","lineNumber":253,"sourceCode":"        if (this._locale === face) this._locale = undefined\n      }\n    }, 'slots.installLocale()')\n  }\n\n  /**\n   * The single ctx-level render entry: the shell renders 'root'; every other\n   * key renders inside components through the props renderSlot face. All\n   * three guards are fail-loud boot-order checks, no fallback.\n   * @param key - must be 'root' (runtime-enforced for dynamically composed callers).\n   * @param owner - owner share for the root entry (the shell supplies {}).\n   * @returns the rendered root tree.\n   */\n  renderSlot<K extends keyof SlotMap & string>(key: K, owner: OwnerOf<K>): ReturnType<SlotRenderer['renderRoot']> {\n    // Widened: in this package's own program SlotMap holds only 'root', which\n    // would fold the guard to constant-false; the check exists for plain-JS\n    // and cross-program callers where K is wider.\n    if ((key as string) !== 'root') {\n      throw new Error(`ctx-level renderSlot only renders 'root' (got \"${key}\"); child slots render through the component props face`)\n    }\n    if (this._renderer === undefined) {\n      throw new Error(\"slot renderer not installed — boot must call ctx.slots.install(createSlotRenderer()) before rendering 'root'\")\n    }\n    if (this._core.entries('root').length === 0) {\n      throw new Error(\"'root' has no registration — a layout entry must register into 'root' before the shell renders it\")\n    }\n    return this._renderer.renderRoot(this.hostFace(), owner)\n  }\n\n  /**\n   * Drop the per-session store instances of a dead session (the sessions\n   * service calls this on scope teardown; root-scoped records are untouched).\n   * Persisted state goes with the session — a never-rendered dead session can\n   * still own keys from an earlier page load, so the instance is materialized\n   * transiently just to clear storage (no-op for unpersisted stores).\n   * @param sessionId - the torn-down session.\n   */","sourceCodeStart":235,"sourceCodeEnd":271,"githubUrl":"https://github.com/deepseek-ai/deepseek-harness/blob/b150a551b8d465e31e418e1b2eaf5e79bbb7d28e/packages/client/runtime/src/client/slots.ts#L235-L271","documentation":"The context-level slot renderer (ctx.slots.renderSlot) exists only for the shell to render the 'root' slot; every nested slot renders through the component props face (the render-slots share handed to a registered component). The guard rejects any non-root key at runtime. It exists because this package's own TypeScript program narrows SlotMap to just 'root' — making the check constant-false and elidable there — while plain-JavaScript or cross-program callers can pass a wider K and must still be stopped.","triggerScenarios":"Calling ctx.slots.renderSlot with any key other than 'root' — e.g. a composed slot name like 'tool.call.toolview' — from shell code or from JavaScript; casting a dynamic key with `as 'root'` or a widened `keyof SlotMap & string` so the compiler accepts it (the runtime guard still throws).","commonSituations":"Trying to render a nested plugin slot directly from app-shell code instead of composing through slots.register; dynamic slot routing driven by config-supplied keys; JS consumers of the client runtime skipping the types entirely.","solutions":["Render nested slots from inside the owning component through its props render-slots face, never from ctx.slots","Keep ctx.slots.renderSlot('root', owner) as the single shell-level render call","Map config-supplied slot keys to a registered component's children instead of calling ctx-level renderSlot","Remove `as 'root'`-style casts — they hide the misuse until this runtime throw"],"exampleFix":"// before — a cast defeats the type; the runtime guard still throws\nctx.slots.renderSlot(dynamicKey as 'root', owner)\n\n// after — only the shell renders 'root'; nested slots go through the component props face\nif (dynamicKey === 'root') {\n  ctx.slots.renderSlot('root', owner)\n} else {\n  // render via the registered component's render-slots share (its children face), not ctx.slots\n}","handlingStrategy":"type-guard","validationCode":"// guard dynamic keys before the ctx-level render\nif (key !== 'root') {\n  throw new Error(`cannot ctx-render slot ${key} — route it through the component props face`)\n}\nctx.slots.renderSlot(key, owner)","typeGuard":"function isRootSlot(key: string): key is 'root' {\n  return key === 'root'\n}","tryCatchPattern":null,"preventionTips":["Reserve ctx.slots.renderSlot for the shell's single 'root' render","Declare nested slots in register({ children }) and render them through the component's props face","Never cast slot keys to satisfy the type — the runtime guard is the designed backstop"],"tags":["slots","ui-composition","render-slot","api-misuse"],"backgroundTag":"invalid-slot-key","analyzedSha":"b150a551b8d465e31e418e1b2eaf5e79bbb7d28e","analyzedAt":"2026-08-24T18:12:29.105Z","schemaVersion":2},"datasetVersion":"2026-08-24T22:17:12.610Z"}