From 58d637f919f5c27526e772acd168fd3e5e057c44 Mon Sep 17 00:00:00 2001 From: Christian Gunderman Date: Fri, 20 Feb 2026 22:28:55 +0000 Subject: [PATCH] Disallow and suppress unsafe assignment (#19736) --- eslint.config.js | 1 + packages/a2a-server/src/commands/restore.ts | 1 + packages/a2a-server/src/config/settings.ts | 3 ++- packages/a2a-server/src/http/app.ts | 11 ++++++++--- packages/a2a-server/src/persistence/gcs.ts | 3 +++ packages/cli/src/config/extension-manager.ts | 1 + packages/cli/src/config/extensionRegistryClient.ts | 1 + packages/cli/src/test-utils/AppRig.tsx | 3 ++- packages/cli/src/test-utils/customMatchers.ts | 2 +- packages/cli/src/test-utils/mockCommandContext.ts | 9 +++++++-- packages/cli/src/test-utils/settings.ts | 3 ++- packages/cli/src/ui/components/SettingsDialog.tsx | 1 + .../cli/src/ui/components/triage/TriageDuplicates.tsx | 1 + .../cli/src/ui/components/triage/TriageIssues.tsx | 1 + packages/cli/src/ui/hooks/useAtCompletion.ts | 2 ++ packages/cli/src/ui/hooks/useSessionBrowser.ts | 1 + packages/cli/src/ui/hooks/useSlashCompletion.ts | 1 + packages/cli/src/ui/hooks/useStateAndRef.ts | 1 + packages/cli/src/ui/utils/TableRenderer.tsx | 1 + packages/cli/src/ui/utils/terminalSetup.ts | 1 + packages/cli/src/ui/utils/textUtils.ts | 2 ++ packages/cli/src/utils/envVarResolver.ts | 1 + packages/cli/src/utils/gitUtils.ts | 1 + packages/cli/src/utils/jsonoutput.ts | 1 + packages/cli/src/utils/persistentState.ts | 1 + packages/cli/src/utils/readStdin.ts | 1 + packages/cli/src/utils/sessionUtils.ts | 2 ++ packages/cli/src/utils/settingsUtils.ts | 1 + packages/cli/src/zed-integration/acpErrors.ts | 2 ++ packages/cli/src/zed-integration/fileSystemService.ts | 1 + packages/cli/src/zed-integration/zedIntegration.ts | 1 + packages/core/src/agents/acknowledgedAgents.ts | 1 + packages/core/src/agents/agent-scheduler.ts | 1 + packages/core/src/agents/local-executor.ts | 3 ++- packages/core/src/agents/registry.ts | 1 + packages/core/src/code_assist/admin/admin_controls.ts | 1 + .../core/src/code_assist/experiments/experiments.ts | 1 + .../core/src/code_assist/oauth-credential-storage.ts | 1 + packages/core/src/code_assist/oauth2.ts | 1 + packages/core/src/core/logger.ts | 2 ++ packages/core/src/hooks/hookRunner.ts | 8 +++++--- packages/core/src/hooks/trustedHooks.ts | 1 + packages/core/src/ide/ide-client.ts | 7 +++++-- packages/core/src/ide/ide-connection-utils.ts | 5 +++++ packages/core/src/ide/process-utils.ts | 1 + packages/core/src/mcp/oauth-provider.test.ts | 3 +-- packages/core/src/mcp/oauth-utils.ts | 1 + .../src/mcp/token-storage/keychain-token-storage.ts | 2 ++ packages/core/src/safety/checker-runner.ts | 1 + packages/core/src/services/sessionSummaryUtils.ts | 3 +++ packages/core/src/services/shellExecutionService.ts | 6 ++++++ .../src/telemetry/clearcut-logger/clearcut-logger.ts | 1 + .../core/src/telemetry/integration.test.circular.ts | 3 +++ packages/core/src/telemetry/loggers.test.circular.ts | 3 +++ packages/core/src/telemetry/loggers.ts | 2 ++ packages/core/src/tools/mcp-client.ts | 4 +++- packages/core/src/tools/ripGrep.ts | 4 ++++ packages/core/src/tools/tool-registry.ts | 1 + packages/core/src/tools/xcode-mcp-fix-transport.ts | 5 ++++- packages/core/src/utils/filesearch/fileSearch.ts | 1 + packages/core/src/utils/getPty.ts | 4 ++++ packages/core/src/utils/googleErrors.ts | 7 +++++++ packages/core/src/utils/memoryDiscovery.ts | 2 ++ packages/core/src/utils/safeJsonStringify.ts | 1 + packages/core/src/utils/schemaValidator.ts | 8 +++++--- packages/core/src/utils/shell-utils.ts | 1 + packages/core/src/utils/stdio.ts | 2 ++ packages/core/src/utils/userAccountManager.ts | 3 +++ packages/devtools/src/index.ts | 1 + packages/sdk/src/session.ts | 2 ++ packages/vscode-ide-companion/src/extension.ts | 4 ++++ 71 files changed, 149 insertions(+), 22 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 7dc3b5ace7..e2a7fa3e8a 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -200,6 +200,7 @@ export default tseslint.config( ignores: ['**/*.test.ts', '**/*.test.tsx'], rules: { '@typescript-eslint/no-unsafe-type-assertion': 'error', + '@typescript-eslint/no-unsafe-assignment': 'error', }, }, { diff --git a/packages/a2a-server/src/commands/restore.ts b/packages/a2a-server/src/commands/restore.ts index 5b4839a2e4..c7567a3b24 100644 --- a/packages/a2a-server/src/commands/restore.ts +++ b/packages/a2a-server/src/commands/restore.ts @@ -69,6 +69,7 @@ export class RestoreCommand implements Command { throw error; } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const toolCallData = JSON.parse(data); const ToolCallDataSchema = getToolCallDataSchema(); const parseResult = ToolCallDataSchema.safeParse(toolCallData); diff --git a/packages/a2a-server/src/config/settings.ts b/packages/a2a-server/src/config/settings.ts index 8d15247128..3f4528c3b2 100644 --- a/packages/a2a-server/src/config/settings.ts +++ b/packages/a2a-server/src/config/settings.ts @@ -7,8 +7,8 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; -import type { MCPServerConfig } from '@google/gemini-cli-core'; import { + type MCPServerConfig, debugLogger, GEMINI_DIR, getErrorMessage, @@ -122,6 +122,7 @@ export function loadSettings(workspaceDir: string): Settings { function resolveEnvVarsInString(value: string): string { const envVarRegex = /\$(?:(\w+)|{([^}]+)})/g; // Find $VAR_NAME or ${VAR_NAME} return value.replace(envVarRegex, (match, varName1, varName2) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const varName = varName1 || varName2; if (process && process.env && typeof process.env[varName] === 'string') { return process.env[varName]; diff --git a/packages/a2a-server/src/http/app.ts b/packages/a2a-server/src/http/app.ts index c061d4e3b3..161139279b 100644 --- a/packages/a2a-server/src/http/app.ts +++ b/packages/a2a-server/src/http/app.ts @@ -7,8 +7,8 @@ import express from 'express'; import type { AgentCard, Message } from '@a2a-js/sdk'; -import type { TaskStore } from '@a2a-js/sdk/server'; import { + type TaskStore, DefaultRequestHandler, InMemoryTaskStore, DefaultExecutionEventBus, @@ -25,9 +25,12 @@ import { loadConfig, loadEnvironment, setTargetDir } from '../config/config.js'; import { loadSettings } from '../config/settings.js'; import { loadExtensions } from '../config/extension.js'; import { commandRegistry } from '../commands/command-registry.js'; -import { debugLogger, SimpleExtensionLoader } from '@google/gemini-cli-core'; +import { + debugLogger, + SimpleExtensionLoader, + GitService, +} from '@google/gemini-cli-core'; import type { Command, CommandArgument } from '../commands/types.js'; -import { GitService } from '@google/gemini-cli-core'; type CommandResponse = { name: string; @@ -88,6 +91,7 @@ async function handleExecuteCommand( }, ) { logger.info('[CoreAgent] Received /executeCommand request: ', req.body); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { command, args } = req.body; try { if (typeof command !== 'string') { @@ -211,6 +215,7 @@ export async function createApp() { const agentSettings = req.body.agentSettings as | AgentSettings | undefined; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const contextId = req.body.contextId || uuidv4(); const wrapper = await agentExecutor.createTask( taskId, diff --git a/packages/a2a-server/src/persistence/gcs.ts b/packages/a2a-server/src/persistence/gcs.ts index ec6b86e56a..215b45837f 100644 --- a/packages/a2a-server/src/persistence/gcs.ts +++ b/packages/a2a-server/src/persistence/gcs.ts @@ -246,6 +246,7 @@ export class GCSTaskStore implements TaskStore { } const [compressedMetadata] = await metadataFile.download(); const jsonData = gunzipSync(compressedMetadata).toString(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const loadedMetadata = JSON.parse(jsonData); logger.info(`Task ${taskId} metadata loaded from GCS.`); @@ -282,12 +283,14 @@ export class GCSTaskStore implements TaskStore { return { id: taskId, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment contextId: loadedMetadata._contextId || uuidv4(), kind: 'task', status: { state: persistedState._taskState, timestamp: new Date().toISOString(), }, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment metadata: loadedMetadata, history: [], artifacts: [], diff --git a/packages/cli/src/config/extension-manager.ts b/packages/cli/src/config/extension-manager.ts index 4756b95b97..179959d83e 100644 --- a/packages/cli/src/config/extension-manager.ts +++ b/packages/cli/src/config/extension-manager.ts @@ -866,6 +866,7 @@ Would you like to attempt to install via "git clone" instead?`, try { const hooksContent = await fs.promises.readFile(hooksFilePath, 'utf-8'); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const rawHooks = JSON.parse(hooksContent); if ( diff --git a/packages/cli/src/config/extensionRegistryClient.ts b/packages/cli/src/config/extensionRegistryClient.ts index 3735f0a798..073b78ad79 100644 --- a/packages/cli/src/config/extensionRegistryClient.ts +++ b/packages/cli/src/config/extensionRegistryClient.ts @@ -81,6 +81,7 @@ export class ExtensionRegistryClient { `${ext.extensionName} ${ext.extensionDescription} ${ext.fullName}`, fuzzy: true, }); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const results = await fzf.find(query); return results.map((r: { item: RegistryExtension }) => r.item); } diff --git a/packages/cli/src/test-utils/AppRig.tsx b/packages/cli/src/test-utils/AppRig.tsx index a0884ee024..018ce1502b 100644 --- a/packages/cli/src/test-utils/AppRig.tsx +++ b/packages/cli/src/test-utils/AppRig.tsx @@ -217,13 +217,14 @@ export class AppRig { } private stubRefreshAuth() { - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment const gcConfig = this.config as any; gcConfig.refreshAuth = async (authMethod: AuthType) => { gcConfig.modelAvailabilityService.reset(); const newContentGeneratorConfig = { authType: authMethod, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment proxy: gcConfig.getProxy(), apiKey: process.env['GEMINI_API_KEY'] || 'test-api-key', }; diff --git a/packages/cli/src/test-utils/customMatchers.ts b/packages/cli/src/test-utils/customMatchers.ts index 0351c7011c..0259c064a6 100644 --- a/packages/cli/src/test-utils/customMatchers.ts +++ b/packages/cli/src/test-utils/customMatchers.ts @@ -21,7 +21,7 @@ import type { TextBuffer } from '../ui/components/shared/text-buffer.js'; const invalidCharsRegex = /[\b\x1b]/; function toHaveOnlyValidCharacters(this: Assertion, buffer: TextBuffer) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-unsafe-assignment const { isNot } = this as any; let pass = true; const invalidLines: Array<{ line: number; content: string }> = []; diff --git a/packages/cli/src/test-utils/mockCommandContext.ts b/packages/cli/src/test-utils/mockCommandContext.ts index c2f1bbcfd3..6bf7aafdbc 100644 --- a/packages/cli/src/test-utils/mockCommandContext.ts +++ b/packages/cli/src/test-utils/mockCommandContext.ts @@ -45,7 +45,7 @@ export const createMockCommandContext = ( forScope: vi.fn().mockReturnValue({ settings: {} }), } as unknown as LoadedSettings, git: undefined as GitService | undefined, - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-unsafe-assignment logger: { log: vi.fn(), logMessage: vi.fn(), @@ -54,7 +54,7 @@ export const createMockCommandContext = ( // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any, // Cast because Logger is a class. }, - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-unsafe-assignment ui: { addItem: vi.fn(), clear: vi.fn(), @@ -94,11 +94,14 @@ export const createMockCommandContext = ( // eslint-disable-next-line @typescript-eslint/no-explicit-any const merge = (target: any, source: any): any => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const output = { ...target }; for (const key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const sourceValue = source[key]; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const targetValue = output[key]; if ( @@ -106,9 +109,11 @@ export const createMockCommandContext = ( Object.prototype.toString.call(sourceValue) === '[object Object]' && Object.prototype.toString.call(targetValue) === '[object Object]' ) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment output[key] = merge(targetValue, sourceValue); } else { // If not, we do a direct assignment. This preserves Date objects and others. + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment output[key] = sourceValue; } } diff --git a/packages/cli/src/test-utils/settings.ts b/packages/cli/src/test-utils/settings.ts index 77e8450a9c..dd498b6625 100644 --- a/packages/cli/src/test-utils/settings.ts +++ b/packages/cli/src/test-utils/settings.ts @@ -46,6 +46,7 @@ export const createMockSettings = ( workspace, isTrusted, errors, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment merged: mergedOverride, ...settingsOverrides } = overrides; @@ -75,7 +76,7 @@ export const createMockSettings = ( // Assign any function overrides (e.g., vi.fn() for methods) for (const key in overrides) { if (typeof overrides[key] === 'function') { - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-unsafe-assignment (loaded as any)[key] = overrides[key]; } } diff --git a/packages/cli/src/ui/components/SettingsDialog.tsx b/packages/cli/src/ui/components/SettingsDialog.tsx index e95692275a..e426e9bbe3 100644 --- a/packages/cli/src/ui/components/SettingsDialog.tsx +++ b/packages/cli/src/ui/components/SettingsDialog.tsx @@ -115,6 +115,7 @@ export function SettingsDialog({ } const doSearch = async () => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const results = await fzfInstance.find(searchQuery); if (!active) return; diff --git a/packages/cli/src/ui/components/triage/TriageDuplicates.tsx b/packages/cli/src/ui/components/triage/TriageDuplicates.tsx index abc749b6d3..878cacfed0 100644 --- a/packages/cli/src/ui/components/triage/TriageDuplicates.tsx +++ b/packages/cli/src/ui/components/triage/TriageDuplicates.tsx @@ -451,6 +451,7 @@ Return a JSON object with: '--limit', String(limit), ]); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const issues: Issue[] = JSON.parse(stdout); if (issues.length === 0) { setState((s) => ({ diff --git a/packages/cli/src/ui/components/triage/TriageIssues.tsx b/packages/cli/src/ui/components/triage/TriageIssues.tsx index 3a654a40de..595384a124 100644 --- a/packages/cli/src/ui/components/triage/TriageIssues.tsx +++ b/packages/cli/src/ui/components/triage/TriageIssues.tsx @@ -137,6 +137,7 @@ export const TriageIssues = ({ '--limit', String(limit), ]); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const issues: Issue[] = JSON.parse(stdout); if (issues.length === 0) { setState((s) => ({ diff --git a/packages/cli/src/ui/hooks/useAtCompletion.ts b/packages/cli/src/ui/hooks/useAtCompletion.ts index a4c5317de8..af827f1b12 100644 --- a/packages/cli/src/ui/hooks/useAtCompletion.ts +++ b/packages/cli/src/ui/hooks/useAtCompletion.ts @@ -166,6 +166,7 @@ async function searchResourceCandidates( const fzf = new AsyncFzf(candidates, { selector: (candidate: ResourceSuggestionCandidate) => candidate.searchKey, }); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const results = await fzf.find(normalizedPattern, { limit: MAX_SUGGESTIONS_TO_SHOW * 3, }); @@ -188,6 +189,7 @@ async function searchAgentCandidates( const fzf = new AsyncFzf(candidates, { selector: (s: Suggestion) => s.label, }); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const results = await fzf.find(normalizedPattern, { limit: MAX_SUGGESTIONS_TO_SHOW, }); diff --git a/packages/cli/src/ui/hooks/useSessionBrowser.ts b/packages/cli/src/ui/hooks/useSessionBrowser.ts index de6495c3b9..79baacbee1 100644 --- a/packages/cli/src/ui/hooks/useSessionBrowser.ts +++ b/packages/cli/src/ui/hooks/useSessionBrowser.ts @@ -57,6 +57,7 @@ export const useSessionBrowser = ( const originalFilePath = path.join(chatsDir, fileName); // Load up the conversation. + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const conversation: ConversationRecord = JSON.parse( await fs.readFile(originalFilePath, 'utf8'), ); diff --git a/packages/cli/src/ui/hooks/useSlashCompletion.ts b/packages/cli/src/ui/hooks/useSlashCompletion.ts index 7809d6cf0f..a53a469571 100644 --- a/packages/cli/src/ui/hooks/useSlashCompletion.ts +++ b/packages/cli/src/ui/hooks/useSlashCompletion.ts @@ -271,6 +271,7 @@ function useCommandSuggestions( const fzfInstance = getFzfForCommands(commandsToSearch); if (fzfInstance) { try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const fzfResults = await fzfInstance.fzf.find(partial); if (signal.aborted) return; const uniqueCommands = new Set(); diff --git a/packages/cli/src/ui/hooks/useStateAndRef.ts b/packages/cli/src/ui/hooks/useStateAndRef.ts index d8dce68ec8..5d2a81a92c 100644 --- a/packages/cli/src/ui/hooks/useStateAndRef.ts +++ b/packages/cli/src/ui/hooks/useStateAndRef.ts @@ -22,6 +22,7 @@ export const useStateAndRef = < (newStateOrCallback) => { let newValue: T; if (typeof newStateOrCallback === 'function') { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment newValue = newStateOrCallback(ref.current); } else { newValue = newStateOrCallback; diff --git a/packages/cli/src/ui/utils/TableRenderer.tsx b/packages/cli/src/ui/utils/TableRenderer.tsx index 4689d461ff..e57e2f7faa 100644 --- a/packages/cli/src/ui/utils/TableRenderer.tsx +++ b/packages/cli/src/ui/utils/TableRenderer.tsx @@ -243,6 +243,7 @@ export const TableRenderer: React.FC = ({ isHeader = false, ): React.ReactNode => { const renderedCells = cells.map((cell, index) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const width = adjustedWidths[index] || 0; return renderCell(cell, width, isHeader); }); diff --git a/packages/cli/src/ui/utils/terminalSetup.ts b/packages/cli/src/ui/utils/terminalSetup.ts index 820497cc2f..5132df4996 100644 --- a/packages/cli/src/ui/utils/terminalSetup.ts +++ b/packages/cli/src/ui/utils/terminalSetup.ts @@ -179,6 +179,7 @@ async function configureVSCodeStyle( await backupFile(keybindingsFile); try { const cleanContent = stripJsonComments(content); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const parsedContent = JSON.parse(cleanContent); if (!Array.isArray(parsedContent)) { return { diff --git a/packages/cli/src/ui/utils/textUtils.ts b/packages/cli/src/ui/utils/textUtils.ts index bd7e2aca75..a039a43991 100644 --- a/packages/cli/src/ui/utils/textUtils.ts +++ b/packages/cli/src/ui/utils/textUtils.ts @@ -233,7 +233,9 @@ export function escapeAnsiCtrlCodes(obj: T): T { let newArr: unknown[] | null = null; for (let i = 0; i < obj.length; i++) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const value = obj[i]; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const escapedValue = escapeAnsiCtrlCodes(value); if (escapedValue !== value) { if (newArr === null) { diff --git a/packages/cli/src/utils/envVarResolver.ts b/packages/cli/src/utils/envVarResolver.ts index fac43682a5..4bd1d6f82f 100644 --- a/packages/cli/src/utils/envVarResolver.ts +++ b/packages/cli/src/utils/envVarResolver.ts @@ -23,6 +23,7 @@ export function resolveEnvVarsInString( ): string { const envVarRegex = /\$(?:(\w+)|{([^}]+)})/g; // Find $VAR_NAME or ${VAR_NAME} return value.replace(envVarRegex, (match, varName1, varName2) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const varName = varName1 || varName2; if (customEnv && typeof customEnv[varName] === 'string') { return customEnv[varName]; diff --git a/packages/cli/src/utils/gitUtils.ts b/packages/cli/src/utils/gitUtils.ts index b415dadc6c..c25366f72a 100644 --- a/packages/cli/src/utils/gitUtils.ts +++ b/packages/cli/src/utils/gitUtils.ts @@ -78,6 +78,7 @@ export const getLatestGitHubRelease = async ( ); } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const releaseTag = (await response.json()).tag_name; if (!releaseTag) { throw new Error(`Response did not include tag_name field`); diff --git a/packages/cli/src/utils/jsonoutput.ts b/packages/cli/src/utils/jsonoutput.ts index ae170ec591..f600b7e165 100644 --- a/packages/cli/src/utils/jsonoutput.ts +++ b/packages/cli/src/utils/jsonoutput.ts @@ -29,6 +29,7 @@ export function tryParseJSON(input: string): object | null { if (!checkInput(input)) return null; const trimmed = input.trim(); try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const parsed = JSON.parse(trimmed); if (parsed === null || typeof parsed !== 'object') { return null; diff --git a/packages/cli/src/utils/persistentState.ts b/packages/cli/src/utils/persistentState.ts index 4fc51d0e14..6ad5695097 100644 --- a/packages/cli/src/utils/persistentState.ts +++ b/packages/cli/src/utils/persistentState.ts @@ -38,6 +38,7 @@ export class PersistentState { const filePath = this.getPath(); if (fs.existsSync(filePath)) { const content = fs.readFileSync(filePath, 'utf-8'); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment this.cache = JSON.parse(content); } else { this.cache = {}; diff --git a/packages/cli/src/utils/readStdin.ts b/packages/cli/src/utils/readStdin.ts index 30834e0d14..3b5b17fe04 100644 --- a/packages/cli/src/utils/readStdin.ts +++ b/packages/cli/src/utils/readStdin.ts @@ -23,6 +23,7 @@ export async function readStdin(): Promise { const onReadable = () => { let chunk; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment while ((chunk = process.stdin.read()) !== null) { if (pipedInputTimerId) { clearTimeout(pipedInputTimerId); diff --git a/packages/cli/src/utils/sessionUtils.ts b/packages/cli/src/utils/sessionUtils.ts index bc2cfbc0e2..559a04dccf 100644 --- a/packages/cli/src/utils/sessionUtils.ts +++ b/packages/cli/src/utils/sessionUtils.ts @@ -254,6 +254,7 @@ export const getAllSessionFiles = async ( async (file): Promise => { const filePath = path.join(chatsDir, file); try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const content: ConversationRecord = JSON.parse( await fs.readFile(filePath, 'utf8'), ); @@ -498,6 +499,7 @@ export class SessionSelector { const sessionPath = path.join(chatsDir, sessionInfo.fileName); try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const sessionData: ConversationRecord = JSON.parse( await fs.readFile(sessionPath, 'utf8'), ); diff --git a/packages/cli/src/utils/settingsUtils.ts b/packages/cli/src/utils/settingsUtils.ts index f5aa18a41e..8d313cf688 100644 --- a/packages/cli/src/utils/settingsUtils.ts +++ b/packages/cli/src/utils/settingsUtils.ts @@ -371,6 +371,7 @@ export function setPendingSettingValue( pendingSettings: Settings, ): Settings { const path = key.split('.'); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const newSettings = JSON.parse(JSON.stringify(pendingSettings)); setNestedValue(newSettings, path, value); return newSettings; diff --git a/packages/cli/src/zed-integration/acpErrors.ts b/packages/cli/src/zed-integration/acpErrors.ts index 2e111b2876..57067115bf 100644 --- a/packages/cli/src/zed-integration/acpErrors.ts +++ b/packages/cli/src/zed-integration/acpErrors.ts @@ -25,7 +25,9 @@ function extractRecursiveMessage(input: string): string { (trimmed.startsWith('[') && trimmed.endsWith(']')) ) { try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const parsed = JSON.parse(trimmed); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const next = parsed?.error?.message || parsed?.[0]?.error?.message || diff --git a/packages/cli/src/zed-integration/fileSystemService.ts b/packages/cli/src/zed-integration/fileSystemService.ts index 51a32f2779..fc4672b4b5 100644 --- a/packages/cli/src/zed-integration/fileSystemService.ts +++ b/packages/cli/src/zed-integration/fileSystemService.ts @@ -23,6 +23,7 @@ export class AcpFileSystemService implements FileSystemService { return this.fallback.readTextFile(filePath); } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const response = await this.connection.readTextFile({ path: filePath, sessionId: this.sessionId, diff --git a/packages/cli/src/zed-integration/zedIntegration.ts b/packages/cli/src/zed-integration/zedIntegration.ts index f04caf01f7..6f18cab7d6 100644 --- a/packages/cli/src/zed-integration/zedIntegration.ts +++ b/packages/cli/src/zed-integration/zedIntegration.ts @@ -702,6 +702,7 @@ export class Session { }, }; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const output = await this.connection.requestPermission(params); const outcome = output.outcome.outcome === CoreToolCallStatus.Cancelled diff --git a/packages/core/src/agents/acknowledgedAgents.ts b/packages/core/src/agents/acknowledgedAgents.ts index 230b62443a..98c90afb96 100644 --- a/packages/core/src/agents/acknowledgedAgents.ts +++ b/packages/core/src/agents/acknowledgedAgents.ts @@ -27,6 +27,7 @@ export class AcknowledgedAgentsService { const filePath = Storage.getAcknowledgedAgentsPath(); try { const content = await fs.readFile(filePath, 'utf-8'); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment this.acknowledgedAgents = JSON.parse(content); } catch (error: unknown) { if (!isNodeError(error) || error.code !== 'ENOENT') { diff --git a/packages/core/src/agents/agent-scheduler.ts b/packages/core/src/agents/agent-scheduler.ts index 4b2e0fa587..ecb4ed960a 100644 --- a/packages/core/src/agents/agent-scheduler.ts +++ b/packages/core/src/agents/agent-scheduler.ts @@ -54,6 +54,7 @@ export async function scheduleAgentTools( } = options; // Create a proxy/override of the config to provide the agent-specific tool registry. + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const agentConfig: Config = Object.create(config); agentConfig.getToolRegistry = () => toolRegistry; diff --git a/packages/core/src/agents/local-executor.ts b/packages/core/src/agents/local-executor.ts index bcb4e888ce..e6557785db 100644 --- a/packages/core/src/agents/local-executor.ts +++ b/packages/core/src/agents/local-executor.ts @@ -34,6 +34,7 @@ import { AgentStartEvent, AgentFinishEvent, RecoveryAttemptEvent, + LlmRole, } from '../telemetry/types.js'; import type { LocalAgentDefinition, @@ -59,7 +60,6 @@ import { getVersion } from '../utils/version.js'; import { getToolCallContext } from '../utils/toolCallContext.js'; import { scheduleAgentTools } from './agent-scheduler.js'; import { DeadlineTimer } from '../utils/deadlineTimer.js'; -import { LlmRole } from '../telemetry/types.js'; import { formatUserHintsForModel } from '../utils/fastAckHelper.js'; /** A callback function to report on agent activity. */ @@ -925,6 +925,7 @@ export class LocalAgentExecutor { continue; } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const validatedOutput = validationResult.data; if (this.definition.processOutput) { submittedOutput = this.definition.processOutput(validatedOutput); diff --git a/packages/core/src/agents/registry.ts b/packages/core/src/agents/registry.ts index 85747c3964..bcd4d65878 100644 --- a/packages/core/src/agents/registry.ts +++ b/packages/core/src/agents/registry.ts @@ -394,6 +394,7 @@ export class AgentRegistry { } // Use Object.create to preserve lazy getters on the definition object + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const merged: LocalAgentDefinition = Object.create(definition); if (overrides.runConfig) { diff --git a/packages/core/src/code_assist/admin/admin_controls.ts b/packages/core/src/code_assist/admin/admin_controls.ts index d4117c2107..d18fcf3d66 100644 --- a/packages/core/src/code_assist/admin/admin_controls.ts +++ b/packages/core/src/code_assist/admin/admin_controls.ts @@ -31,6 +31,7 @@ export function sanitizeAdminSettings( if (sanitized.mcpSetting?.mcpConfigJson) { try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const parsed = JSON.parse(sanitized.mcpSetting.mcpConfigJson); const validationResult = McpConfigDefinitionSchema.safeParse(parsed); diff --git a/packages/core/src/code_assist/experiments/experiments.ts b/packages/core/src/code_assist/experiments/experiments.ts index 94b99b311a..b46f88a17f 100644 --- a/packages/core/src/code_assist/experiments/experiments.ts +++ b/packages/core/src/code_assist/experiments/experiments.ts @@ -35,6 +35,7 @@ export async function getExperiments( const expPath = process.env['GEMINI_EXP']; debugLogger.debug('Reading experiments from', expPath); const content = await fs.promises.readFile(expPath, 'utf8'); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const response: ListExperimentsResponse = JSON.parse(content); if ( (response.flags && !Array.isArray(response.flags)) || diff --git a/packages/core/src/code_assist/oauth-credential-storage.ts b/packages/core/src/code_assist/oauth-credential-storage.ts index 39163384b9..c7c0209cfa 100644 --- a/packages/core/src/code_assist/oauth-credential-storage.ts +++ b/packages/core/src/code_assist/oauth-credential-storage.ts @@ -125,6 +125,7 @@ export class OAuthCredentialStorage { throw error; } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const credentials: Credentials = JSON.parse(credsJson); // Save to new storage diff --git a/packages/core/src/code_assist/oauth2.ts b/packages/core/src/code_assist/oauth2.ts index e5ad7e584a..fd2f5ea178 100644 --- a/packages/core/src/code_assist/oauth2.ts +++ b/packages/core/src/code_assist/oauth2.ts @@ -695,6 +695,7 @@ async function fetchAndCacheUserInfo(client: OAuth2Client): Promise { return; } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const userInfo = await response.json(); await userAccountManager.cacheGoogleAccount(userInfo.email); } catch (error) { diff --git a/packages/core/src/core/logger.ts b/packages/core/src/core/logger.ts index 83f4183ce4..362601f895 100644 --- a/packages/core/src/core/logger.ts +++ b/packages/core/src/core/logger.ts @@ -88,6 +88,7 @@ export class Logger { } try { const fileContent = await fs.readFile(this.logFilePath, 'utf-8'); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const parsedLogs = JSON.parse(fileContent); if (!Array.isArray(parsedLogs)) { debugLogger.debug( @@ -352,6 +353,7 @@ export class Logger { const path = await this._getCheckpointPath(tag); try { const fileContent = await fs.readFile(path, 'utf-8'); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const parsedContent = JSON.parse(fileContent); // Handle legacy format (just an array of Content) diff --git a/packages/core/src/hooks/hookRunner.ts b/packages/core/src/hooks/hookRunner.ts index 47706b0eb4..05f81d1983 100644 --- a/packages/core/src/hooks/hookRunner.ts +++ b/packages/core/src/hooks/hookRunner.ts @@ -5,10 +5,8 @@ */ import { spawn } from 'node:child_process'; -import type { HookConfig } from './types.js'; -import { HookEventName, ConfigSource } from './types.js'; -import type { Config } from '../config/config.js'; import type { + HookConfig, HookInput, HookOutput, HookExecutionResult, @@ -17,6 +15,8 @@ import type { BeforeModelOutput, BeforeToolInput, } from './types.js'; +import { HookEventName, ConfigSource } from './types.js'; +import type { Config } from '../config/config.js'; import type { LLMRequest } from './hookTranslator.js'; import { debugLogger } from '../utils/debugLogger.js'; import { sanitizeEnvironment } from '../services/environmentSanitization.js'; @@ -356,8 +356,10 @@ export class HookRunner { const textToParse = stdout.trim() || stderr.trim(); if (textToParse) { try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment let parsed = JSON.parse(textToParse); if (typeof parsed === 'string') { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment parsed = JSON.parse(parsed); } if (parsed && typeof parsed === 'object') { diff --git a/packages/core/src/hooks/trustedHooks.ts b/packages/core/src/hooks/trustedHooks.ts index 1c9b5b5f18..562f0e76bb 100644 --- a/packages/core/src/hooks/trustedHooks.ts +++ b/packages/core/src/hooks/trustedHooks.ts @@ -34,6 +34,7 @@ export class TrustedHooksManager { try { if (fs.existsSync(this.configPath)) { const content = fs.readFileSync(this.configPath, 'utf-8'); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment this.trustedHooks = JSON.parse(content); } } catch (error) { diff --git a/packages/core/src/ide/ide-client.ts b/packages/core/src/ide/ide-client.ts index 58067d70ab..2b3b29ac3c 100644 --- a/packages/core/src/ide/ide-client.ts +++ b/packages/core/src/ide/ide-client.ts @@ -16,8 +16,10 @@ import { getIdeProcessInfo } from './process-utils.js'; import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; -import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js'; -import { ListToolsResultSchema } from '@modelcontextprotocol/sdk/types.js'; +import { + CallToolResultSchema, + ListToolsResultSchema, +} from '@modelcontextprotocol/sdk/types.js'; import { IDE_REQUEST_TIMEOUT_MS } from './constants.js'; import { debugLogger } from '../utils/debugLogger.js'; import { @@ -343,6 +345,7 @@ export class IdeClient { if (textPart?.text) { try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const parsedJson = JSON.parse(textPart.text); if (parsedJson && typeof parsedJson.content === 'string') { return parsedJson.content; diff --git a/packages/core/src/ide/ide-connection-utils.ts b/packages/core/src/ide/ide-connection-utils.ts index ce01b5997c..81a9740327 100644 --- a/packages/core/src/ide/ide-connection-utils.ts +++ b/packages/core/src/ide/ide-connection-utils.ts @@ -89,8 +89,10 @@ export function getStdioConfigFromEnv(): StdioConfig | undefined { let args: string[] = []; if (argsStr) { try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const parsedArgs = JSON.parse(argsStr); if (Array.isArray(parsedArgs)) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment args = parsedArgs; } else { logger.error( @@ -188,6 +190,7 @@ export async function getConnectionConfigFromFile( } if (validWorkspaces.length === 1) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const selected = validWorkspaces[0]; const fileIndex = parsedContents.indexOf(selected); if (fileIndex !== -1) { @@ -202,6 +205,7 @@ export async function getConnectionConfigFromFile( (content) => String(content.port) === portFromEnv, ); if (matchingPortIndex !== -1) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const selected = validWorkspaces[matchingPortIndex]; const fileIndex = parsedContents.indexOf(selected); if (fileIndex !== -1) { @@ -213,6 +217,7 @@ export async function getConnectionConfigFromFile( } } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const selected = validWorkspaces[0]; const fileIndex = parsedContents.indexOf(selected); if (fileIndex !== -1) { diff --git a/packages/core/src/ide/process-utils.ts b/packages/core/src/ide/process-utils.ts index 5c1ca570a6..4b4680df51 100644 --- a/packages/core/src/ide/process-utils.ts +++ b/packages/core/src/ide/process-utils.ts @@ -47,6 +47,7 @@ async function getProcessTableWindows(): Promise> { let processes: RawProcessInfo | RawProcessInfo[]; try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment processes = JSON.parse(stdout); } catch (_e) { return processMap; diff --git a/packages/core/src/mcp/oauth-provider.test.ts b/packages/core/src/mcp/oauth-provider.test.ts index facefe176a..77c46305a6 100644 --- a/packages/core/src/mcp/oauth-provider.test.ts +++ b/packages/core/src/mcp/oauth-provider.test.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { vi } from 'vitest'; +import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest'; // Mock dependencies AT THE TOP const mockOpenBrowserSecurely = vi.hoisted(() => vi.fn()); @@ -54,7 +54,6 @@ vi.mock('node:readline', () => ({ })), })); -import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import * as http from 'node:http'; import * as crypto from 'node:crypto'; import type { diff --git a/packages/core/src/mcp/oauth-utils.ts b/packages/core/src/mcp/oauth-utils.ts index 815ed7c089..320c3b9685 100644 --- a/packages/core/src/mcp/oauth-utils.ts +++ b/packages/core/src/mcp/oauth-utils.ts @@ -409,6 +409,7 @@ export class OAuthUtils { */ static parseTokenExpiry(idToken: string): number | undefined { try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const payload = JSON.parse( Buffer.from(idToken.split('.')[1], 'base64').toString(), ); diff --git a/packages/core/src/mcp/token-storage/keychain-token-storage.ts b/packages/core/src/mcp/token-storage/keychain-token-storage.ts index 8936823196..4be0d082e5 100644 --- a/packages/core/src/mcp/token-storage/keychain-token-storage.ts +++ b/packages/core/src/mcp/token-storage/keychain-token-storage.ts @@ -45,7 +45,9 @@ export class KeychainTokenStorage try { // Try to import keytar without any timeout - let the OS handle it const moduleName = 'keytar'; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const module = await import(moduleName); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment this.keytarModule = module.default || module; } catch (_) { //Keytar is optional so we shouldn't raise an error of log anything. diff --git a/packages/core/src/safety/checker-runner.ts b/packages/core/src/safety/checker-runner.ts index 02f824d980..a46c3e6dbd 100644 --- a/packages/core/src/safety/checker-runner.ts +++ b/packages/core/src/safety/checker-runner.ts @@ -229,6 +229,7 @@ export class CheckerRunner { // Try to parse the output try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const rawResult = JSON.parse(stdout); const result = SafetyCheckResultSchema.parse(rawResult); diff --git a/packages/core/src/services/sessionSummaryUtils.ts b/packages/core/src/services/sessionSummaryUtils.ts index ed51cecd2b..c64f19870d 100644 --- a/packages/core/src/services/sessionSummaryUtils.ts +++ b/packages/core/src/services/sessionSummaryUtils.ts @@ -26,6 +26,7 @@ async function generateAndSaveSummary( ): Promise { // Read session file const content = await fs.readFile(sessionPath, 'utf-8'); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const conversation: ConversationRecord = JSON.parse(content); // Skip if summary already exists @@ -69,6 +70,7 @@ async function generateAndSaveSummary( // Re-read the file before writing to handle race conditions const freshContent = await fs.readFile(sessionPath, 'utf-8'); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const freshConversation: ConversationRecord = JSON.parse(freshContent); // Check if summary was added by another process @@ -127,6 +129,7 @@ export async function getPreviousSession( try { const content = await fs.readFile(filePath, 'utf-8'); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const conversation: ConversationRecord = JSON.parse(content); if (conversation.summary) { diff --git a/packages/core/src/services/shellExecutionService.ts b/packages/core/src/services/shellExecutionService.ts index 96cae8c269..c21eeb1136 100644 --- a/packages/core/src/services/shellExecutionService.ts +++ b/packages/core/src/services/shellExecutionService.ts @@ -568,6 +568,7 @@ export class ShellExecutionService { const guardedCommand = ensurePromptvarsDisabled(commandToExecute, shell); const args = [...argsPrefix, guardedCommand]; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const ptyProcess = ptyInfo.module.spawn(executable, args, { cwd, name: 'xterm-256color', @@ -598,6 +599,7 @@ export class ShellExecutionService { headlessTerminal.scrollToTop(); this.activePtys.set(ptyProcess.pid, { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment ptyProcess, headlessTerminal, maxSerializedLines: shellExecutionConfig.maxSerializedLines, @@ -831,6 +833,7 @@ export class ShellExecutionService { signal: signal ?? null, error, aborted: abortSignal.aborted, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment pid: ptyProcess.pid, executionMethod: ptyInfo?.name ?? 'node-pty', }); @@ -862,9 +865,11 @@ export class ShellExecutionService { const abortHandler = async () => { if (ptyProcess.pid && !exited) { await killProcessGroup({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment pid: ptyProcess.pid, escalate: true, isExited: () => exited, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment pty: ptyProcess, }); } @@ -873,6 +878,7 @@ export class ShellExecutionService { abortSignal.addEventListener('abort', abortHandler, { once: true }); }); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment return { pid: ptyProcess.pid, result }; } catch (e) { // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion diff --git a/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts b/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts index d0407aa0d6..9a0900d86d 100644 --- a/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts +++ b/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts @@ -716,6 +716,7 @@ export class ClearcutLogger { event.function_name === ASK_USER_TOOL_NAME && event.metadata['ask_user'] ) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const askUser = event.metadata['ask_user']; const askUserMapping: { [key: string]: EventMetadataKey } = { question_types: EventMetadataKey.GEMINI_CLI_ASK_USER_QUESTION_TYPES, diff --git a/packages/core/src/telemetry/integration.test.circular.ts b/packages/core/src/telemetry/integration.test.circular.ts index af09b3f8b0..6cb9e84faf 100644 --- a/packages/core/src/telemetry/integration.test.circular.ts +++ b/packages/core/src/telemetry/integration.test.circular.ts @@ -36,11 +36,13 @@ describe('Circular Reference Integration Test', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const socketLike: any = { _httpMessage: { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment agent: proxyAgentLike, socket: null, }, }; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment socketLike._httpMessage.socket = socketLike; // Create circular reference proxyAgentLike.sockets['cloudcode-pa.googleapis.com:443'] = [socketLike]; @@ -49,6 +51,7 @@ describe('Circular Reference Integration Test', () => { error: new Error('Network error'), function_args: { filePath: '/test/file.txt', + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment httpAgent: proxyAgentLike, // This would cause the circular reference }, }; diff --git a/packages/core/src/telemetry/loggers.test.circular.ts b/packages/core/src/telemetry/loggers.test.circular.ts index b8c365cd2c..d6b6ea86ce 100644 --- a/packages/core/src/telemetry/loggers.test.circular.ts +++ b/packages/core/src/telemetry/loggers.test.circular.ts @@ -39,8 +39,10 @@ describe('Circular Reference Handling', () => { sockets: {}, agent: null, }; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment circularObject.agent = circularObject; // Create circular reference circularObject.sockets['test-host'] = [ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment { _httpMessage: { agent: circularObject } }, ]; @@ -48,6 +50,7 @@ describe('Circular Reference Handling', () => { const mockRequest: ToolCallRequestInfo = { callId: 'test-call-id', name: 'ReadFile', + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment args: circularObject, // This would cause the original error isClientInitiated: false, prompt_id: 'test-prompt-id', diff --git a/packages/core/src/telemetry/loggers.ts b/packages/core/src/telemetry/loggers.ts index 54bec22a65..b4935f3af7 100644 --- a/packages/core/src/telemetry/loggers.ts +++ b/packages/core/src/telemetry/loggers.ts @@ -145,12 +145,14 @@ export function logToolCall(config: Config, event: ToolCallEvent): void { }); if (event.metadata) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const added = event.metadata['model_added_lines']; if (typeof added === 'number' && added > 0) { recordLinesChanged(config, added, 'added', { function_name: event.function_name, }); } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const removed = event.metadata['model_removed_lines']; if (typeof removed === 'number' && removed > 0) { recordLinesChanged(config, removed, 'removed', { diff --git a/packages/core/src/tools/mcp-client.ts b/packages/core/src/tools/mcp-client.ts index ccc6bbec3c..5e802e8157 100644 --- a/packages/core/src/tools/mcp-client.ts +++ b/packages/core/src/tools/mcp-client.ts @@ -22,6 +22,7 @@ import type { Prompt, ReadResourceResult, Resource, + Tool as McpTool, } from '@modelcontextprotocol/sdk/types.js'; import { ListResourcesResultSchema, @@ -31,7 +32,6 @@ import { ToolListChangedNotificationSchema, PromptListChangedNotificationSchema, ProgressNotificationSchema, - type Tool as McpTool, } from '@modelcontextprotocol/sdk/types.js'; import { ApprovalMode, PolicyDecision } from '../policy/types.js'; import { parse } from 'shell-quote'; @@ -1996,6 +1996,7 @@ export async function createTransport( // The `XcodeMcpBridgeFixTransport` wrapper hides the underlying `StdioClientTransport`, // which exposes `stderr` for debug logging. We need to unwrap it to attach the listener. + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const underlyingTransport = transport instanceof XcodeMcpBridgeFixTransport ? // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion @@ -2007,6 +2008,7 @@ export async function createTransport( underlyingTransport.stderr ) { underlyingTransport.stderr.on('data', (data) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const stderrStr = data.toString().trim(); debugLogger.debug( `[DEBUG] [MCP STDERR (${mcpServerName})]: `, diff --git a/packages/core/src/tools/ripGrep.ts b/packages/core/src/tools/ripGrep.ts index 5fe516c335..7f49774c05 100644 --- a/packages/core/src/tools/ripGrep.ts +++ b/packages/core/src/tools/ripGrep.ts @@ -481,8 +481,10 @@ class GrepToolInvocation extends BaseToolInvocation< basePath: string, ): GrepMatch | null { try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const json = JSON.parse(line); if (json.type === 'match' || json.type === 'context') { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const data = json.data; // Defensive check: ensure text properties exist (skips binary/invalid encoding) if (data.path?.text && data.lines?.text) { @@ -500,7 +502,9 @@ class GrepToolInvocation extends BaseToolInvocation< return { filePath: relativeFilePath || path.basename(absoluteFilePath), + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment lineNumber: data.line_number, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment line: data.lines.text.trimEnd(), isContext: json.type === 'context', }; diff --git a/packages/core/src/tools/tool-registry.ts b/packages/core/src/tools/tool-registry.ts index abcf34e1f8..95bac200be 100644 --- a/packages/core/src/tools/tool-registry.ts +++ b/packages/core/src/tools/tool-registry.ts @@ -390,6 +390,7 @@ export class ToolRegistry { // execute discovery command and extract function declarations (w/ or w/o "tool" wrappers) const functions: FunctionDeclaration[] = []; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const discoveredItems = JSON.parse(stdout.trim()); if (!discoveredItems || !Array.isArray(discoveredItems)) { diff --git a/packages/core/src/tools/xcode-mcp-fix-transport.ts b/packages/core/src/tools/xcode-mcp-fix-transport.ts index 7daabef87e..9f7785e8c9 100644 --- a/packages/core/src/tools/xcode-mcp-fix-transport.ts +++ b/packages/core/src/tools/xcode-mcp-fix-transport.ts @@ -75,7 +75,7 @@ export class XcodeMcpBridgeFixTransport // We can cast because we verified 'result' is in response, // but TS might still be picky if the type is a strict union. // Let's treat it safely. - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-unsafe-assignment const result = response.result as any; // Check if we have content but missing structuredContent @@ -85,12 +85,15 @@ export class XcodeMcpBridgeFixTransport result.content.length > 0 && !result.structuredContent ) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const firstItem = result.content[0]; if (firstItem.type === 'text' && typeof firstItem.text === 'string') { try { // Attempt to parse the text as JSON + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const parsed = JSON.parse(firstItem.text); // If successful, populate structuredContent + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment result.structuredContent = parsed; } catch (_) { // Ignored: Content is likely plain text, not JSON. diff --git a/packages/core/src/utils/filesearch/fileSearch.ts b/packages/core/src/utils/filesearch/fileSearch.ts index 6aedaf7276..21d1e19168 100644 --- a/packages/core/src/utils/filesearch/fileSearch.ts +++ b/packages/core/src/utils/filesearch/fileSearch.ts @@ -145,6 +145,7 @@ class RecursiveFileSearch implements FileSearch { if (pattern.includes('*') || !this.fzf) { filteredCandidates = await filter(candidates, pattern, options.signal); } else { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment filteredCandidates = await this.fzf .find(pattern) .then((results: Array>) => diff --git a/packages/core/src/utils/getPty.ts b/packages/core/src/utils/getPty.ts index 35712b6de3..b5d53ca473 100644 --- a/packages/core/src/utils/getPty.ts +++ b/packages/core/src/utils/getPty.ts @@ -23,12 +23,16 @@ export const getPty = async (): Promise => { } try { const lydell = '@lydell/node-pty'; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const module = await import(lydell); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment return { module, name: 'lydell-node-pty' }; } catch (_e) { try { const nodePty = 'node-pty'; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const module = await import(nodePty); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment return { module, name: 'node-pty' }; } catch (_e2) { return null; diff --git a/packages/core/src/utils/googleErrors.ts b/packages/core/src/utils/googleErrors.ts index 70c7098118..3ae74f0de8 100644 --- a/packages/core/src/utils/googleErrors.ts +++ b/packages/core/src/utils/googleErrors.ts @@ -166,10 +166,12 @@ export function parseGoogleApiError(error: unknown): GoogleApiError | null { depth < maxDepth ) { try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const parsedMessage = JSON.parse( currentError.message.replace(/\u00A0/g, '').replace(/\n/g, ' '), ); if (parsedMessage.error) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment currentError = parsedMessage.error; depth++; } else { @@ -243,6 +245,7 @@ function fromGaxiosError(errorObj: object): ErrorShape | undefined { if (typeof data === 'string') { try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment data = JSON.parse(data); } catch (_) { // Not a JSON string, can't parse. @@ -250,6 +253,7 @@ function fromGaxiosError(errorObj: object): ErrorShape | undefined { } if (Array.isArray(data) && data.length > 0) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment data = data[0]; } @@ -288,6 +292,7 @@ function fromApiError(errorObj: object): ErrorShape | undefined { if (typeof data === 'string') { try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment data = JSON.parse(data); } catch (_) { // Not a JSON string, can't parse. @@ -297,6 +302,7 @@ function fromApiError(errorObj: object): ErrorShape | undefined { const lastBrace = data.lastIndexOf('}'); if (firstBrace !== -1 && lastBrace !== -1 && lastBrace > firstBrace) { try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment data = JSON.parse(data.substring(firstBrace, lastBrace + 1)); } catch (__) { // Still failed @@ -307,6 +313,7 @@ function fromApiError(errorObj: object): ErrorShape | undefined { } if (Array.isArray(data) && data.length > 0) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment data = data[0]; } diff --git a/packages/core/src/utils/memoryDiscovery.ts b/packages/core/src/utils/memoryDiscovery.ts index aef6ff50b5..c35d009e1d 100644 --- a/packages/core/src/utils/memoryDiscovery.ts +++ b/packages/core/src/utils/memoryDiscovery.ts @@ -127,6 +127,7 @@ async function getGeminiMdFilePathsInternal( result.value.global.forEach((p) => globalPaths.add(p)); result.value.project.forEach((p) => projectPaths.add(p)); } else { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const error = result.reason; const message = error instanceof Error ? error.message : String(error); logger.error(`Error discovering files in directory: ${message}`); @@ -299,6 +300,7 @@ export async function readGeminiMdFiles( } else { // This case shouldn't happen since we catch all errors above, // but handle it for completeness + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const error = result.reason; const message = error instanceof Error ? error.message : String(error); logger.error(`Unexpected error processing file: ${message}`); diff --git a/packages/core/src/utils/safeJsonStringify.ts b/packages/core/src/utils/safeJsonStringify.ts index fd03e7965d..611519d09b 100644 --- a/packages/core/src/utils/safeJsonStringify.ts +++ b/packages/core/src/utils/safeJsonStringify.ts @@ -37,6 +37,7 @@ export function safeJsonStringify( function removeEmptyObjects(data: any): object { const cleanedObject: { [key: string]: unknown } = {}; for (const k in data) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const v = data[k]; if (v !== null && v !== undefined && typeof v === 'boolean') { cleanedObject[k] = v; diff --git a/packages/core/src/utils/schemaValidator.ts b/packages/core/src/utils/schemaValidator.ts index 8d8579f647..e58b7b8d9b 100644 --- a/packages/core/src/utils/schemaValidator.ts +++ b/packages/core/src/utils/schemaValidator.ts @@ -12,9 +12,9 @@ import * as addFormats from 'ajv-formats'; import { debugLogger } from './debugLogger.js'; // Ajv's ESM/CJS interop: use 'any' for compatibility as recommended by Ajv docs -// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion +// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-unsafe-assignment const AjvClass = (AjvPkg as any).default || AjvPkg; -// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion +// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-unsafe-assignment const Ajv2020Class = (Ajv2020Pkg as any).default || Ajv2020Pkg; const ajvOptions = { @@ -29,12 +29,14 @@ const ajvOptions = { }; // Draft-07 validator (default) +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const ajvDefault: Ajv = new AjvClass(ajvOptions); // Draft-2020-12 validator for MCP servers using rmcp +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const ajv2020: Ajv = new Ajv2020Class(ajvOptions); -// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion +// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-unsafe-assignment const addFormatsFunc = (addFormats as any).default || addFormats; addFormatsFunc(ajvDefault); addFormatsFunc(ajv2020); diff --git a/packages/core/src/utils/shell-utils.ts b/packages/core/src/utils/shell-utils.ts index 7daeb063f5..6f92ec6386 100644 --- a/packages/core/src/utils/shell-utils.ts +++ b/packages/core/src/utils/shell-utils.ts @@ -479,6 +479,7 @@ function parsePowerShellCommandDetails( hasRedirection?: boolean; } | null = null; try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment parsed = JSON.parse(output); } catch { return { details: [], hasError: true }; diff --git a/packages/core/src/utils/stdio.ts b/packages/core/src/utils/stdio.ts index 8f62906399..22fd2e8447 100644 --- a/packages/core/src/utils/stdio.ts +++ b/packages/core/src/utils/stdio.ts @@ -88,6 +88,7 @@ export function createWorkingStdio() { if (prop === 'write') { return writeToStdout; } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const value = Reflect.get(target, prop, receiver); if (typeof value === 'function') { return value.bind(target); @@ -101,6 +102,7 @@ export function createWorkingStdio() { if (prop === 'write') { return writeToStderr; } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const value = Reflect.get(target, prop, receiver); if (typeof value === 'function') { return value.bind(target); diff --git a/packages/core/src/utils/userAccountManager.ts b/packages/core/src/utils/userAccountManager.ts index 4434a18027..342565a6ca 100644 --- a/packages/core/src/utils/userAccountManager.ts +++ b/packages/core/src/utils/userAccountManager.ts @@ -30,6 +30,7 @@ export class UserAccountManager { return defaultState; } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const parsed = JSON.parse(content); // Inlined validation logic @@ -50,7 +51,9 @@ export class UserAccountManager { } return { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment active: parsed.active ?? null, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment old: parsed.old ?? [], }; } diff --git a/packages/devtools/src/index.ts b/packages/devtools/src/index.ts index 81cb909957..7f8fea8d3d 100644 --- a/packages/devtools/src/index.ts +++ b/packages/devtools/src/index.ts @@ -260,6 +260,7 @@ export class DevTools extends EventEmitter { ws.on('message', (data: Buffer) => { try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const message = JSON.parse(data.toString()); // Handle registration first diff --git a/packages/sdk/src/session.ts b/packages/sdk/src/session.ts index d5e6b9ee8a..8332ef29d0 100644 --- a/packages/sdk/src/session.ts +++ b/packages/sdk/src/session.ts @@ -210,6 +210,7 @@ export class GeminiCliSession { const toolCall = event.value; let args = toolCall.args; if (typeof args === 'string') { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment args = JSON.parse(args); } toolCallsToSchedule.push({ @@ -238,6 +239,7 @@ export class GeminiCliSession { }; const originalRegistry = this.config.getToolRegistry(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const scopedRegistry: ToolRegistry = Object.create(originalRegistry); scopedRegistry.getTool = (name: string) => { const tool = originalRegistry.getTool(name); diff --git a/packages/vscode-ide-companion/src/extension.ts b/packages/vscode-ide-companion/src/extension.ts index bcfe6e40dd..456ec6e872 100644 --- a/packages/vscode-ide-companion/src/extension.ts +++ b/packages/vscode-ide-companion/src/extension.ts @@ -38,6 +38,7 @@ async function checkForUpdates( isManagedExtensionSurface: boolean, ) { try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const currentVersion = context.extension.packageJSON.version; // Fetch extension details from the VSCode Marketplace. @@ -75,9 +76,12 @@ async function checkForUpdates( return; } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const data = await response.json(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const extension = data?.results?.[0]?.extensions?.[0]; // The versions are sorted by date, so the first one is the latest. + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const latestVersion = extension?.versions?.[0]?.version; if (