Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 7 additions & 16 deletions packages/opencode/src/cli/cmd/debug/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,24 +91,15 @@ function parseToolParams(input?: string) {
const trimmed = input.trim()
if (trimmed.length === 0) return {}

const parsed = iife(() => {
try {
return JSON.parse(trimmed)
} catch (jsonError) {
try {
return new Function(`return (${trimmed})`)()
} catch (evalError) {
throw new Error(
`Failed to parse --params. Use JSON or a JS object literal. JSON error: ${jsonError}. Eval error: ${evalError}.`,
)
}
try {
const parsed = JSON.parse(trimmed)
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
throw new Error("Tool params must be an object.")
}
})

if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
throw new Error("Tool params must be an object.")
return parsed as Record<string, unknown>
} catch (e) {
throw new Error(`Failed to parse --params as JSON: ${e instanceof Error ? e.message : e}`)
}
Comment on lines +94 to 102

Copilot AI Feb 2, 2026

Copy link

Choose a reason for hiding this comment

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

The error handling logic has an issue: when JSON parsing succeeds but the validation fails (line 97 throws "Tool params must be an object"), the catch block wraps it with "Failed to parse --params as JSON" which is misleading since JSON parsing actually succeeded. Consider moving the validation outside the try-catch block or handling validation errors separately to provide accurate error messages.

Copilot uses AI. Check for mistakes.
return parsed as Record<string, unknown>
}

async function createToolContext(agent: Agent.Info) {
Expand Down
2 changes: 1 addition & 1 deletion packages/opencode/src/mcp/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -801,7 +801,7 @@ export namespace MCP {

// Register the callback BEFORE opening the browser to avoid race condition
// when the IdP has an active SSO session and redirects immediately
const callbackPromise = McpOAuthCallback.waitForCallback(oauthState)
const callbackPromise = McpOAuthCallback.waitForCallback(oauthState, mcpName)

try {
const subprocess = await open(authorizationUrl)
Expand Down
23 changes: 15 additions & 8 deletions packages/opencode/src/mcp/oauth-callback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import { OAUTH_CALLBACK_PORT, OAUTH_CALLBACK_PATH } from "./oauth-provider"

const log = Log.create({ service: "mcp.oauth-callback" })

function escapeHtml(str: string): string {
return str.replace(/[&<>"']/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[char] ?? char)
}

const HTML_SUCCESS = `<!DOCTYPE html>
<html>
<head>
Expand Down Expand Up @@ -39,12 +43,13 @@ const HTML_ERROR = (error: string) => `<!DOCTYPE html>
<div class="container">
<h1>Authorization Failed</h1>
<p>An error occurred during authorization.</p>
<div class="error">${error}</div>
<div class="error">${escapeHtml(error)}</div>
</div>
</body>
</html>`

interface PendingAuth {
mcpName: string
resolve: (code: string) => void
reject: (error: Error) => void
timeout: ReturnType<typeof setTimeout>
Expand Down Expand Up @@ -136,7 +141,7 @@ export namespace McpOAuthCallback {
log.info("oauth callback server started", { port: OAUTH_CALLBACK_PORT })
}

export function waitForCallback(oauthState: string): Promise<string> {
export function waitForCallback(oauthState: string, mcpName: string): Promise<string> {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
if (pendingAuths.has(oauthState)) {
Expand All @@ -145,16 +150,18 @@ export namespace McpOAuthCallback {
}
}, CALLBACK_TIMEOUT_MS)

pendingAuths.set(oauthState, { resolve, reject, timeout })
pendingAuths.set(oauthState, { mcpName, resolve, reject, timeout })
})
}

export function cancelPending(mcpName: string): void {
const pending = pendingAuths.get(mcpName)
if (pending) {
clearTimeout(pending.timeout)
pendingAuths.delete(mcpName)
pending.reject(new Error("Authorization cancelled"))
for (const [state, pending] of pendingAuths) {
if (pending.mcpName === mcpName) {
clearTimeout(pending.timeout)
pendingAuths.delete(state)
pending.reject(new Error("Authorization cancelled"))
return
}
}
}

Expand Down
8 changes: 7 additions & 1 deletion packages/opencode/src/tool/read.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,13 @@ export const ReadTool = Tool.define("read", {
const dir = path.dirname(filepath)
const base = path.basename(filepath)

const dirEntries = fs.readdirSync(dir)
const dirEntries = (() => {
try {
return fs.readdirSync(dir)
} catch {
return []
}
})()
const suggestions = dirEntries
.filter(
(entry) =>
Expand Down
Loading