π File detail
utils/permissions/PermissionPromptToolResultSchema.ts
π― Use case
This file lives under βutils/β, which covers cross-cutting helpers (shell, tempfiles, settings, messages, process input, β¦). On the API surface it exposes inputSchema, Input, outputSchema, Output, and permissionPromptToolResultToPermissionDecision β mainly functions, hooks, or classes. Dependencies touch src and schema validation. It composes internal code from debug, lazySchema, PermissionResult, PermissionUpdate, and PermissionUpdateSchema (relative imports).
Generated from folder role, exports, dependency roots, and inline comments β not hand-reviewed for every path.
π§ Inline summary
import type { Tool, ToolUseContext } from 'src/Tool.js' import z from 'zod/v4' import { logForDebugging } from '../debug.js' import { lazySchema } from '../lazySchema.js' import type {
π€ Exports (heuristic)
inputSchemaInputoutputSchemaOutputpermissionPromptToolResultToPermissionDecision
π External import roots
Package roots from from "β¦" (relative paths omitted).
srczod
π₯οΈ Source preview
import type { Tool, ToolUseContext } from 'src/Tool.js'
import z from 'zod/v4'
import { logForDebugging } from '../debug.js'
import { lazySchema } from '../lazySchema.js'
import type {
PermissionDecision,
PermissionDecisionReason,
} from './PermissionResult.js'
import {
applyPermissionUpdates,
persistPermissionUpdates,
} from './PermissionUpdate.js'
import { permissionUpdateSchema } from './PermissionUpdateSchema.js'
export const inputSchema = lazySchema(() =>
z.object({
tool_name: z
.string()
.describe('The name of the tool requesting permission'),
input: z.record(z.string(), z.unknown()).describe('The input for the tool'),
tool_use_id: z
.string()
.optional()
.describe('The unique tool use request ID'),
}),
)
export type Input = z.infer<ReturnType<typeof inputSchema>>
// Zod schema for permission results
// This schema is used to validate the MCP permission prompt tool
// so we maintain it as a subset of the real PermissionDecision type
// Matches PermissionDecisionClassificationSchema in entrypoints/sdk/coreSchemas.ts.
// Malformed values fall through to undefined (same pattern as updatedPermissions
// below) so a bad string from the SDK host doesn't reject the whole decision.
const decisionClassificationField = lazySchema(() =>
z
.enum(['user_temporary', 'user_permanent', 'user_reject'])
.optional()
.catch(undefined),
)
const PermissionAllowResultSchema = lazySchema(() =>
z.object({
behavior: z.literal('allow'),
updatedInput: z.record(z.string(), z.unknown()),
// SDK hosts may send malformed entries; fall back to undefined rather
// than rejecting the entire allow decision (anthropics/claude-code#29440)
updatedPermissions: z
.array(permissionUpdateSchema())
.optional()
.catch(ctx => {
logForDebugging(
`Malformed updatedPermissions from SDK host ignored: ${ctx.error.issues[0]?.message ?? 'unknown'}`,
{ level: 'warn' },
)
return undefined
}),
toolUseID: z.string().optional(),
decisionClassification: decisionClassificationField(),
}),
)
const PermissionDenyResultSchema = lazySchema(() =>
z.object({
behavior: z.literal('deny'),
message: z.string(),
interrupt: z.boolean().optional(),
toolUseID: z.string().optional(),
decisionClassification: decisionClassificationField(),
}),
)
export const outputSchema = lazySchema(() =>
z.union([PermissionAllowResultSchema(), PermissionDenyResultSchema()]),
)
export type Output = z.infer<ReturnType<typeof outputSchema>>
/**
* Normalizes the result of a permission prompt tool to a PermissionDecision.
*/
export function permissionPromptToolResultToPermissionDecision(
result: Output,
tool: Tool,
input: { [key: string]: unknown },
toolUseContext: ToolUseContext,
): PermissionDecision {
const decisionReason: PermissionDecisionReason = {
type: 'permissionPromptTool',
permissionPromptToolName: tool.name,
toolResult: result,
}
if (result.behavior === 'allow') {
const updatedPermissions = result.updatedPermissions
if (updatedPermissions) {
toolUseContext.setAppState(prev => ({
...prev,
toolPermissionContext: applyPermissionUpdates(
prev.toolPermissionContext,
updatedPermissions,
),
}))
persistPermissionUpdates(updatedPermissions)
}
// Mobile clients responding from a push notification don't have the
// original tool input, so they send `{}` to satisfy the schema. Treat an
// empty object as "use original" so the tool doesn't run with no args.
const updatedInput =
Object.keys(result.updatedInput).length > 0 ? result.updatedInput : input
return {
...result,
updatedInput,
decisionReason,
}
} else if (result.behavior === 'deny' && result.interrupt) {
logForDebugging(
`SDK permission prompt deny+interrupt: tool=${tool.name} message=${result.message}`,
)
toolUseContext.abortController.abort()
}
return {
...result,
decisionReason,
}
}