# `as any` Cleanup Implementation Plan > Scope note: this plan is based on `.hermes/plans/as-any-cleanup-task.md` and targeted reads of the surrounding five lines for the listed `.ts`/`.tsx` source files under `src/` and `packages/`. No source files were edited and no builds were run while preparing this plan. **Goal:** Remove fixable `as any` casts from Anthropic-core decompiled TypeScript while preserving runtime behavior. **Architecture:** Prefer restoring lost discriminated unions, local type guards, and precise wrapper interfaces at the smallest owning boundary. Use `unknown`, `satisfies`, module/global augmentation, and existing domain types instead of `any`. Cases that represent missing CCB/upstream message or SDK schema contracts are marked `NEEDS_UPSTREAM`. **Tech Stack:** TypeScript, React/Ink, MCP SDK schemas, Node/Bun APIs, native computer-use modules. --- ## Execution Rules - Do not edit generated files, test files, community command areas, provider JSON parsing paths, `node_modules`, `dist`, or lockfiles. - For each fix, run a targeted typecheck only after source edits are made in a later implementation pass. - Preserve behavior first. Most replacements should be type-only changes, type guards, or changed declaration shapes. - When a cast is retained for runtime dynamism, replace `as any` with a documented `unknown`-based interface or mark as `NEEDS_UPSTREAM`. ## Recommended Task Order 1. Add shared narrow types for globals, MCP notification schemas, message extensions, permission updates, and native computer-use modules. 2. Replace straightforward local casts with type guards or reordered narrowing. 3. Fix setting/config shape casts by extending the owning config types once. 4. Fix message/attachment access by adding explicit unions or guards near message type definitions. 5. Handle native/dynamic modules with typed optional capability interfaces. 6. Re-run `rg -n "as any" src packages --glob '*.ts' --glob '*.tsx' --glob '!*.d.ts'` and confirm only intentionally excluded areas remain. 7. Run the task's deferred verification command: ```bash export PATH="$HOME/.bun/bin:$PATH" bun run typecheck ``` Expected per the task: typecheck still reports the existing unrelated community-code errors unless those have been fixed separately. ## Inventory And Fix Plan | # | Location | Classification | Fix | |---|---|---|---| | 1 | `src/main.tsx:408` `(global as any).require('inspector')` | FIXABLE | Import `createRequire` from `node:module`, create `const require = createRequire(import.meta.url)`, then load `node:inspector` as `typeof import('node:inspector')`. | | 2 | `src/hooks/useIdeSelection.ts:113` `SelectionChangedSchema() as any` | NEEDS_UPSTREAM | Restore the MCP SDK notification schema type returned by `SelectionChangedSchema`; once the schema factory is typed as the SDK handler parameter type, remove the cast. | | 3 | `src/hooks/useVirtualScroll.ts:186` comment-only `as any` | JUSTIFIED | No code cast exists. Optional grep-cleanup fix: reword the comment to "as suitable as any other range" so literal searches do not count it. | | 4 | `src/hooks/toolPermission/PermissionContext.ts:228` `suggestions as any` | FIXABLE | Align `suggestions` with the hook runner parameter type, likely `PermissionSuggestion[]`; update the `runPermissionRequestHooks` signature if it currently expects a looser decompiled type. | | 5 | `src/hooks/useIdeLogging.ts:30` `LogEventSchema() as any` | NEEDS_UPSTREAM | Restore the MCP notification schema factory type for IDE log events; remove the cast once `setNotificationHandler` accepts the returned schema type directly. | | 6 | `src/hooks/usePromptsFromClaudeInChrome.tsx:51` `ClaudeInChromePromptNotificationSchema() as any` | NEEDS_UPSTREAM | Restore the MCP notification schema factory type for Chrome prompt notifications; keep notification params inferred from the schema. | | 7 | `src/components/FeedbackSurvey/useFrustrationDetection.ts:15` `(m as any).isApiErrorMessage` | FIXABLE | Replace with a type guard, for example `isApiErrorFeedbackMessage(m): m is Message & { isApiErrorMessage: true }`, or use the canonical system API-error message discriminant if available. | | 8 | `src/components/FeedbackSurvey/useFrustrationDetection.ts:28` `'product_feedback' as any` | FIXABLE | Add `'product_feedback'` to the policy key union consumed by `isPolicyAllowed`, or introduce a typed `PolicyKey.ProductFeedback` constant. | | 9 | `src/components/ultraplan/UltraplanChoiceDialog.tsx:97` `(key as any).wheelDown` | FIXABLE | Define `type WheelKey = typeof key & { wheelDown?: boolean; wheelUp?: boolean }` once inside the handler and read `const wheelKey = key as WheelKey`. | | 10 | `src/components/ultraplan/UltraplanChoiceDialog.tsx:98` `(key as any).wheelDown` | FIXABLE | Reuse the `wheelKey.wheelDown` narrow from #9. | | 11 | `src/components/ultraplan/UltraplanChoiceDialog.tsx:100` `(key as any).wheelUp` | FIXABLE | Reuse the `wheelKey.wheelUp` narrow from #9. | | 12 | `src/components/ultraplan/UltraplanChoiceDialog.tsx:101` `(key as any).wheelUp` | FIXABLE | Reuse the `wheelKey.wheelUp` narrow from #9. | | 13 | `src/components/PromptInput/PromptInput.tsx:1802` `previousModeBeforeAuto as any` | FIXABLE | Type `previousModeBeforeAuto` as the same permission mode type used by `toolPermissionContext.mode`, then assign it directly. | | 14 | `src/components/PromptInput/PromptInput.tsx:1808` `previousModeBeforeAuto as any` | FIXABLE | Same as #13; one state type fix removes both casts. | | 15 | `src/components/messages/AttachmentMessage.tsx:146` `attachment as any` | FIXABLE | Add a `ToolDiscoveryAttachment` union member or local guard `isToolDiscoveryAttachment(attachment)` with typed `tools: { name: string }[]`. | | 16 | `src/components/messages/UserTextMessage.tsx:106` `UserGitHubWebhookMessage as any` | FIXABLE | Type the lazy required component as `React.ComponentType<{ addMargin: boolean; param: typeof param }>` before `React.createElement`. | | 17 | `src/components/messages/UserTextMessage.tsx:160` `UserForkBoilerplateMessage as any` | FIXABLE | Use the same `React.ComponentType<{ addMargin: boolean; param: typeof param }>` pattern. | | 18 | `src/components/messages/UserTextMessage.tsx:173` `UserCrossSessionMessage as any` | FIXABLE | Use the same component prop type pattern. | | 19 | `src/components/ConsoleOAuthFlow.tsx:275` `{ modelType: 'anthropic' } as any` | FIXABLE | Add `modelType?: 'anthropic' \| 'openai' \| 'gemini'` to the settings update type accepted by `updateSettingsForSource`. | | 20 | `src/components/ConsoleOAuthFlow.tsx:680` `'anthropic' as any` | FIXABLE | Same settings type fix as #19. | | 21 | `src/components/ConsoleOAuthFlow.tsx:682` settings object `as any` | FIXABLE | Same settings type fix as #19, including `env` shape compatibility. | | 22 | `src/components/ConsoleOAuthFlow.tsx:900` `'openai' as any` | FIXABLE | Same settings type fix as #19. | | 23 | `src/components/ConsoleOAuthFlow.tsx:902` settings object `as any` | FIXABLE | Same settings type fix as #19. | | 24 | `src/components/ConsoleOAuthFlow.tsx:1133` `'gemini' as any` | FIXABLE | Same settings type fix as #19. | | 25 | `src/components/ConsoleOAuthFlow.tsx:1135` settings object `as any` | FIXABLE | Same settings type fix as #19. | | 26 | `src/components/permissions/PermissionRequest.tsx:106` monitor permission component `as any` | FIXABLE | Define a shared `PermissionRequestComponent` prop type and type `MonitorPermissionRequest ?? FallbackPermissionRequest` to that component type. | | 27 | `src/utils/sideQuery.ts:224` `requestParams as any` | FIXABLE | Type `requestParams` as the OpenAI chat completion create params accepted by `client.chat.completions.create`, including tool fields and non-streaming response shape. | | 28 | `src/utils/execFileNoThrowPortable.ts:75` `(execaSync as any)(command, ...)` | FIXABLE | Use the proper execa command API for shell strings or add a typed wrapper with `(command: string, options: ExecaSyncOptions) => ExecaSyncReturnValue`. | | 29 | `src/utils/ultraplan/ccrSession.ts:104` `(m as any).message.content` assistant | FIXABLE | Narrow `m` to the assistant SDK message type and read `m.message.content` through that imported type. | | 30 | `src/utils/ultraplan/ccrSession.ts:112` `(m as any).message.content` user | FIXABLE | Narrow `m` to the user SDK message type and check `Array.isArray(m.message.content)` before iterating. | | 31 | `src/utils/transcriptSearch.ts:100` `(p as any[])` | FIXABLE | Add a local `TextContentBlock` guard and handle `Array.isArray(p)` with `p.flatMap(block => isTextBlock(block) ? [block.text] : [])`. | | 32 | `src/utils/filePersistence/outputsScanner.ts:72` `readdir(...) as any[]` | FIXABLE | Type `entries` as `Awaited>` for the recursive call or as `Dirent[]` if the runtime always returns dirents. | | 33 | `src/utils/filePersistence/outputsScanner.ts:116` `turnStartTime as any as number` | FIXABLE | Normalize once with `const turnStartMs = typeof turnStartTime === 'number' ? turnStartTime : turnStartTime.getTime()` and compare to `turnStartMs`. | | 34 | `src/utils/performanceShim.ts:140` `markResourceTiming: (() => {}) as any` | FIXABLE | Add a local `PerformanceWithUndiciResourceTiming` interface with `markResourceTiming(...args: unknown[]): void`, then type `shim` against it. | | 35 | `src/utils/performanceShim.ts:146` `(original as any).onresourcetimingbufferfull` | FIXABLE | Include `onresourcetimingbufferfull` in the local extended performance interface and cast `original` to that interface, not `any`. | | 36 | `src/utils/performanceShim.ts:162` `(globalThis as any).__performanceShimInstalled` read | FIXABLE | Add global augmentation for `var __performanceShimInstalled: boolean \| undefined`. | | 37 | `src/utils/performanceShim.ts:163` `(globalThis as any).__performanceShimInstalled = true` | FIXABLE | Same global augmentation as #36. | | 38 | `src/utils/settings/validateEditTool.ts:39` `(afterValidation as any).error` | FIXABLE | Ensure `validateSettingsFileContent` returns a discriminated union with `isValid: false`, `error`, and `fullSchema`; TypeScript will narrow inside `if (!afterValidation.isValid)`. | | 39 | `src/utils/settings/validateEditTool.ts:39` `(afterValidation as any).fullSchema` | FIXABLE | Same discriminated union fix as #38. | | 40 | `src/utils/log.ts:233` `readdir(...) as any` | FIXABLE | Remove the cast if the local `files` type already matches, or type the variable as `Dirent[]`. | | 41 | `src/utils/hooks/execAgentHook.ts:214` `(message as any).attachment.type` | NEEDS_UPSTREAM | The `structured_output` attachment shape needs to be restored in the shared message union. After that, narrow `message.type === 'attachment' && message.attachment.type === 'structured_output'`. | | 42 | `src/utils/hooks/execAgentHook.ts:216` `(message as any).attachment.data` | NEEDS_UPSTREAM | Same upstream attachment union fix as #41. | | 43 | `src/utils/computerUse/escHotkey.ts:29` `(cu as any).hotkey?.registerEscape` | FIXABLE | Extend `requireComputerUseSwift`'s return type with optional `hotkey: { registerEscape; unregister; notifyExpectedEscape }`. | | 44 | `src/utils/computerUse/escHotkey.ts:44` `(requireComputerUseSwift() as any).hotkey?.unregister()` | FIXABLE | Same native module return type fix as #43. | | 45 | `src/utils/computerUse/escHotkey.ts:54` `(requireComputerUseSwift() as any).hotkey?.notifyExpectedEscape()` | FIXABLE | Same native module return type fix as #43. | | 46 | `src/utils/computerUse/hostAdapter.ts:83` `(cu as any).tcc` | FIXABLE | Extend the native module return type with optional `tcc.checkAccessibility()` and `tcc.checkScreenRecording()` methods. | | 47 | `src/utils/computerUse/executorCrossPlatform.ts:494` `(this as any).mouseDown()` | FIXABLE | Avoid `this` typing by calling the concrete method through the object name or factoring `mouseDown`/`mouseUp` helpers outside the object literal. | | 48 | `src/utils/computerUse/executorCrossPlatform.ts:499` `(this as any).mouseUp()` | FIXABLE | Same object-method typing fix as #47. | | 49 | `src/utils/computerUse/executorCrossPlatform.ts:556` `action as any` | FIXABLE | Type `manageWindow(action: WindowManagementAction, opts?: WindowManagementOptions)` using the action union expected by `platform.windowManagement.manageWindow`. | | 50 | `src/utils/computerUse/win32/bridgeClient.ts:136` `(writable as any).flush` check | FIXABLE | Introduce `type FlushableWritable = Writable & { flush?: () => void }` and cast `stdin` to that type. | | 51 | `src/utils/computerUse/win32/bridgeClient.ts:137` `(writable as any).flush()` | FIXABLE | Same `FlushableWritable` fix as #50. | | 52 | `src/utils/computerUse/drainRunLoop.ts:21` `(cu as any)?._drainMainRunLoop?.()` | FIXABLE | Extend the native module return type with optional `_drainMainRunLoop?: () => void`. | | 53 | `src/utils/computerUse/common.ts:57` `'none') as any` | FIXABLE | Type `CLI_CU_CAPABILITIES` with `satisfies ComputerUseCapabilities` and use literal unions for `screenshotFiltering`. | | 54 | `src/utils/computerUse/common.ts:62` `'darwin') as any` | FIXABLE | Same `satisfies ComputerUseCapabilities` fix as #53 for `platform`. | | 55 | `src/utils/messageQueueManager.ts:371` `(cmd as any).origin?.kind` | FIXABLE | Add `origin?: { kind: 'channel' \| string }` to `QueuedCommand` or create a local `hasChannelOrigin(cmd)` guard. | | 56 | `src/utils/plugins/marketplaceManager.ts:191` merged marketplace object `as any` | FIXABLE | Type all merged marketplace maps as `Record` and return that type. | | 57 | `src/utils/forkedAgent.ts:565` `(message as any).event?.type` | NEEDS_UPSTREAM | Restore `stream_event` message typing with a discriminated `event` union containing `message_delta`. | | 58 | `src/utils/forkedAgent.ts:566` `(message as any).event.usage` | NEEDS_UPSTREAM | Same stream event union fix as #57. | | 59 | `src/utils/forkedAgent.ts:568` `(message as any).event.usage` | NEEDS_UPSTREAM | Same stream event union fix as #57. | | 60 | `src/utils/deepLink/protocolHandler.ts:97` `(waitForUrlEvent as any)(5000)` | FIXABLE | Add an ambient module declaration for `url-handler-napi` exporting `waitForUrlEvent(timeoutMs: number): string \| Promise \| null`. | | 61 | `src/utils/sliceAnsi.ts:80` `(token as any).value` | FIXABLE | Type lexer tokens as a discriminated union, for example `{ type: 'ansi'; code: string } \| { type: 'text'; value: string; width: number }`, then narrow before reading `value`. | | 62 | `src/utils/plans.ts:370` `(snapshotFiles as any[]).push(...)` | FIXABLE | Define `type SnapshotFile = NonNullable[number]` and initialize `const snapshotFiles: SnapshotFile[] = []`. | | 63 | `src/utils/plans.ts:377` `(snapshotFiles as any[]).length` | FIXABLE | Same `SnapshotFile[]` fix as #62. | | 64 | `src/utils/textHighlighting.ts:131` `(token as any).value.length` | FIXABLE | Share the typed ANSI/text token union from #61 and narrow to the text-token branch before reading `value`. | | 65 | `src/utils/textHighlighting.ts:138` `(token as any).value.length` | FIXABLE | Same token union fix as #64. | | 66 | `src/utils/sideQuestion.ts:139` `(toolUse as any).name` | FIXABLE | Use a guard `isToolUseBlock(block): block is { type: 'tool_use'; name: string }` before reading `name`. | | 67 | `src/utils/sideQuestion.ts:151` `apiErr.error as any` | FIXABLE | Normalize unknown API errors before formatting: `const error = apiErr.error instanceof Error ? apiErr.error : new Error(String(apiErr.error))`. | | 68 | `src/utils/messages.ts:3894` `attachment as any` | FIXABLE | Same `ToolDiscoveryAttachment` union or guard as #15, shared from message attachment types. | | 69 | `src/utils/searchExtraTools.ts:507` `(msg as any).compactMetadata` | FIXABLE | Add `compactMetadata?: { preCompactDiscoveredTools?: string[] }` to the `compact_boundary` system message variant, or use a local guard. | | 70 | `src/utils/effort.ts:81` `'xhigh_effort' as any` | FIXABLE | Add `'xhigh_effort'` to the third-party model capability key union consumed by `get3PModelCapabilityOverride`. | | 71 | `src/utils/sessionRestore.ts:81` `(msg.message!.content as any[])` | FIXABLE | Narrow assistant content with `Array.isArray(msg.message?.content)` and type blocks as the SDK content block union. | | 72 | `src/utils/worktree.ts:1296` `(result as any).baseBranch` | FIXABLE | Update the worktree creation result union so the `!result.existed` branch includes `baseBranch: string`. | | 73 | `src/utils/permissions/permissions.ts:416` `suggestions as any` | FIXABLE | Same permission hook signature alignment as #4. | | 74 | `src/utils/permissions/permissions.ts:427` `decision.updatedPermissions as any` | FIXABLE | Type hook decisions so `updatedPermissions` is `PermissionUpdate[]`, matching `persistPermissionUpdates`. | | 75 | `src/utils/permissions/permissions.ts:432` `decision.updatedPermissions as any` | FIXABLE | Same hook decision type fix as #74, matching `applyPermissionUpdates`. | | 76 | `src/utils/auth.ts:120` `(settings as any).modelType === 'openai'` | FIXABLE | Add `modelType?: 'anthropic' \| 'openai' \| 'gemini'` to the settings type returned by `getSettings_DEPRECATED`. | | 77 | `src/utils/auth.ts:121` `(settings as any).modelType === 'gemini'` | FIXABLE | Same settings type fix as #76. | | 78 | `src/utils/swarm/inProcessRunner.ts:1452` `(m.message?.content ?? []) as any[]` | FIXABLE | Narrow assistant message content with `Array.isArray`, type blocks as SDK content blocks, and use a typed `isTextBlock`/`isToolUseBlock` guard. | | 79 | `src/constants/promptEngineeringAudit.runner.ts:15` `(globalThis as any).MACRO` | FIXABLE | Add or reuse global `MACRO` augmentation and assign `globalThis.MACRO` directly. | | 80 | `src/constants/promptEngineeringAudit.runner.ts:217` `] as any` tools array | FIXABLE | Type `standardTools` as `Tools` or `satisfies Tools` using the expected minimal tool shape. | | 81 | `src/constants/outputStyles.ts:188` `(style as any).source` | FIXABLE | Extend `OutputStyleConfig` with optional plugin metadata fields `source?: 'plugin' \| ...` and `forceForPlugin?: boolean`. | | 82 | `src/constants/outputStyles.ts:189` `(style as any).forceForPlugin` | FIXABLE | Same output style config extension as #81. | | 83 | `src/services/vcr.ts:272` `.filter(Boolean) as any` | FIXABLE | Replace with a typed predicate, for example `.filter((block): block is NonNullable => block !== null && block !== undefined)`. | | 84 | `src/services/tokenEstimation.ts:482` `block.content as any` | FIXABLE | Align `roughTokenCountEstimationForContent` with the tool-result content type or normalize tool result content into the accepted content union first. | | 85 | `src/services/skillSearch/prefetch.ts:244` signal object `as any` | FIXABLE | Define `SkillSearchPrefetchSignal` with `queryText`, `startedAt`, `durationMs`, `indexSize`, and `method: 'tfidf'`, then pass that type to logging. | | 86 | `src/services/skillSearch/prefetch.ts:309` signal object `as any` | FIXABLE | Same `SkillSearchPrefetchSignal` type as #85. | | 87 | `src/services/notifier.ts:139` `plist.parse(...) as any` | FIXABLE | Cast to `unknown` first and validate object shape, or use `as Record` without `any`. | | 88 | `src/tasks/LocalShellTask/killShellTasks.ts:18` `(task as any).status` | FIXABLE | Reorder the condition to `if (!isLocalShellTask(task) || task.status !== 'running')`, so the guard narrows before reading `status`. | | 89 | `src/buddy/companionReact.ts:76` `(m as any).message?.content` | FIXABLE | Narrow to user message type and use a helper `getMessageContent(m): string \| ContentBlock[] \| undefined`. | | 90 | `src/buddy/companionReact.ts:92` `(m as any).message?.content` | FIXABLE | Same message content helper as #89 for user and assistant messages. | | 91 | `src/entrypoints/mcp.ts:147` `(validationResult as any).message` | FIXABLE | Ensure the validation result union has `{ result: false; message: string }`; the `!validationResult.result` branch should then narrow. | | 92 | `src/entrypoints/cli.tsx:13` `(globalThis as any).MACRO` | FIXABLE | Same global `MACRO` augmentation as #79. | | 93 | `src/entrypoints/cli.tsx:149` `ChannelPermissionRequestNotificationSchema() as any` | NEEDS_UPSTREAM | Restore MCP notification schema typing for channel permission request notifications. | | 94a | `packages/builtin-tools/src/tools/FileReadTool/FileReadTool.ts:913` `(extractResult as any).error.message` | FIXABLE | Fix the PDF result discriminated union so `!extractResult.success` narrows to `{ error: { message: string; reason?: string } }`. | | 94b | `packages/builtin-tools/src/tools/FileReadTool/FileReadTool.ts:917` `(extractResult as any).data.file.count` | FIXABLE | Same PDF result union fix as #94a; the success branch should expose `data.file.count`. | | 94c | `packages/builtin-tools/src/tools/FileReadTool/FileReadTool.ts:984` `(extractResult as any).error.reason` | FIXABLE | Same PDF result union fix as #94a; the failure branch should expose `error.reason`. | | 94d | `packages/builtin-tools/src/tools/FileReadTool/FileReadTool.ts:1000` `(readResult as any).error.message` | FIXABLE | Apply the same discriminated result type to `readPDF`. | | 94e | `packages/builtin-tools/src/tools/FileReadTool/FileReadTool.ts:1177` `(sharp as any)(imageBuffer)` | FIXABLE | Type the dynamic sharp import as `typeof import('sharp')` and normalize default export with a typed callable `SharpFactory`. | | 95a | `packages/builtin-tools/src/tools/AgentTool/runAgent.ts:789` `(message as any).event.type` | NEEDS_UPSTREAM | Restore the subagent `stream_event` message type carrying `event: { type: 'message_start' }`. | | 95b | `packages/builtin-tools/src/tools/AgentTool/runAgent.ts:790` `(message as any).ttftMs` check | NEEDS_UPSTREAM | Same subagent stream event type as #95a, with optional `ttftMs: number`. | | 95c | `packages/builtin-tools/src/tools/AgentTool/runAgent.ts:792` `(message as any).ttftMs` use | NEEDS_UPSTREAM | Same subagent stream event type as #95a. | | 95d | `packages/builtin-tools/src/tools/AgentTool/runAgent.ts:799` `(message as any).attachment.type` | NEEDS_UPSTREAM | Restore attachment union member `{ type: 'attachment'; attachment: { type: 'max_turns_reached'; maxTurns: number } }`. | | 95e | `packages/builtin-tools/src/tools/AgentTool/runAgent.ts:808` `(message as any).attachment.maxTurns` | NEEDS_UPSTREAM | Same attachment union member as #95d. | ## Notes On Count Drift - The task file describes "94 `as any`" but also groups multi-cast blocks under single numbered items for `FileReadTool` and `AgentTool`. - The targeted current source scan found 104 literal `as any` tokens in the listed files, including one comment-only hit in `src/hooks/useVirtualScroll.ts:186`. - This plan keeps the task numbering through #94 and labels grouped literal casts as #94a-#94e and #95a-#95e so every actual listed code cast has a concrete classification and fix. ## Expected Outcome - All `FIXABLE` rows can be removed with local type work. - `JUSTIFIED` rows require no type-safety change. - `NEEDS_UPSTREAM` rows should wait for restored MCP/SDK/message schema contracts or be handled in a separate upstream-type restoration task.