[Docs] Missing `tool_input` and `tool_response` Schemes for Hook Development
In claude-code official doc, the reference says PreToolUse Hook and PostToolUse Hook can handle various tool-using events. And input scheme for different tool-using is different. However, I failed to find any reference to these kinds of tool input/response schemes.
As the key document for building with claude-code, it should be improved as soon as possible. Thank you for your efforts.
I agree, the docs should specify tool_input and tool_response.
I have to ask claude to read the cli.js file to get the tool response of edit...
Complete Edit Tool Response Schema
Based on analysis of the Claude Code JavaScript source (cli.js), here are all the fields in the Edit tool's tool_response:
{
filePath: string, // Path to the file that was edited
oldString: string, // The original text that was replaced
newString: string, // The new text that replaced the old text
originalFile: string, // The complete original file contents before editing
structuredPatch: Array<{ // Structured diff showing the changes
oldStart: number,
oldLines: number,
newStart: number,
newLines: number,
lines: string[]
}>,
userModified: boolean, // Whether the user modified Claude's proposed changes
replaceAll: boolean // Whether all occurrences were replaced (not just first)
}
Summary
The Edit tool response contains 7 fields total, not just the 2 (filePath and success) shown in basic documentation examples:
- filePath - the edited file path
- oldString - what was replaced
- newString - what it was replaced with
- originalFile - full original file content
- structuredPatch - detailed diff information as an array of hunks
- userModified - boolean flag if user changed Claude's proposal before accepting
- replaceAll - boolean whether all occurrences were replaced
Note: There is no success field - the presence of the response itself indicates success. Errors are handled through exceptions/error responses rather than a success flag.
For reference:
Not sure how accurate this is.
Complete Claude Code Built-in Tool Schemas
Based on direct analysis of the Claude Code JavaScript source (cli.js), here are all the built-in tool input and output schemas:
1. Edit Tool
Input:
{
file_path: string, // The absolute path to the file to modify
old_string: string, // The text to replace
new_string: string, // The text to replace it with (must be different from old_string)
replace_all?: boolean // Replace all occurrences of old_string (default: false)
}
Output:
{
filePath: string, // The file path that was edited
oldString: string, // The original string that was replaced
newString: string, // The new string that replaced it
originalFile: string, // The original file contents before editing
structuredPatch: Array<{ // Diff patch showing the changes
oldStart: number,
oldLines: number,
newStart: number,
newLines: number,
lines: string[]
}>,
userModified: boolean, // Whether the user modified the proposed changes
replaceAll: boolean // Whether all occurrences were replaced
}
2. Write Tool
Input:
{
file_path: string, // The absolute path to the file to write (must be absolute, not relative)
content: string // The content to write to the file
}
Output:
{
type: "create" | "update", // Whether a new file was created or an existing file was updated
filePath: string, // The path to the file that was written
content: string, // The content that was written to the file
structuredPatch: Array<{ // Diff patch showing the changes (empty array for create)
oldStart: number,
oldLines: number,
newStart: number,
newLines: number,
lines: string[]
}>
}
3. Read Tool
Input:
{
file_path: string, // The absolute path to the file to read
offset?: number, // The line number to start reading from (only if file too large)
limit?: number // The number of lines to read (only if file too large)
}
Output:
// For text files:
{
type: "text",
file: {
filePath: string, // The path to the file that was read
content: string, // The file contents
numLines: number, // Number of lines returned
startLine: number, // Starting line number (1-based)
totalLines: number // Total lines in the file
}
}
// For notebook files (.ipynb):
{
type: "notebook",
file: {
filePath: string,
cells: Array<{...}> // Notebook cells
}
}
// For image files:
{
type: "image",
file: {
base64: string, // Base64-encoded image data
type: string // MIME type (e.g., "image/png")
}
}
// For PDF files:
{
type: "pdf",
file: {
filePath: string,
base64: string,
originalSize: number
}
}
4. MultiEdit Tool
Input:
{
file_path: string, // The absolute path to the file to modify
edits: Array<{ // Array of edit operations to perform sequentially (min 1 required)
old_string: string,
new_string: string,
replace_all?: boolean // default: false
}>
}
Output:
{
filePath: string, // The file path that was edited
originalFileContents: string, // The original file contents before edits
structuredPatch: Array<{ // Array of diff hunks showing changes
oldStart: number,
oldLines: number,
newStart: number,
newLines: number,
lines: string[]
}>,
edits: Array<{ // The edits that were applied
old_string: string,
new_string: string,
replace_all: boolean
}>,
userModified: boolean // Whether user modified the changes
}
5. Bash Tool
Input:
{
command: string, // The command to execute
timeout?: number, // Optional timeout in milliseconds (max 600000)
description?: string, // Clear, concise description in 5-10 words
run_in_background?: boolean // Set to true to run in background
}
Output:
{
stdout: string, // Standard output
stderr: string, // Standard error
summary?: string, // AI-generated summary (if enabled)
rawOutputPath?: string, // Path to raw output file (if summarized)
interrupted: boolean, // Whether the command was interrupted
isImage: boolean, // Whether output contains image data
returnCodeInterpretation?: string, // Human-readable interpretation of return code
backgroundTaskId?: string, // ID if run in background
dangerouslyOverrideSandbox?: boolean // Whether sandbox was overridden
}
6. BashOutput Tool
Input:
{
bash_id: string, // The ID of the background shell to retrieve output from
filter?: string // Optional regex to filter output lines
}
Output:
{
stdout: string, // Standard output from background process
stderr: string // Standard error from background process
}
7. Glob Tool
Input:
{
pattern: string, // The glob pattern to match files against
path?: string // Directory to search in (defaults to cwd if omitted)
}
Output:
{
filenames: string[], // Array of matching file paths
durationMs: number, // Time taken to perform the search
numFiles: number, // Number of files matched
truncated: boolean // Whether results were truncated
}
8. Grep Tool
Input:
{
pattern: string, // Regex pattern to search for
path?: string, // File or directory to search in
glob?: string, // Glob pattern to filter files
output_mode?: "content" | "files_with_matches" | "count", // Default: "files_with_matches"
"-B"?: number, // Lines before match (content mode only)
"-A"?: number, // Lines after match (content mode only)
"-C"?: number, // Lines before and after (content mode only)
"-n"?: boolean, // Show line numbers (content mode only)
"-i"?: boolean, // Case insensitive search
type?: string, // File type (js, py, rust, etc.)
head_limit?: number, // Limit output to first N entries
multiline?: boolean // Enable multiline mode (default: false)
}
Output:
{
mode: "content" | "files_with_matches" | "count",
numFiles: number,
filenames: string[],
content?: string, // For content mode
numLines?: number, // For content mode
numMatches?: number // Number of matches found
}
9. TodoWrite Tool
Input:
{
todos: Array<{
content: string, // The todo description
status: "pending" | "in_progress" | "completed",
activeForm: string // Present continuous form (e.g., "Running tests")
}>
}
Output:
{
success: boolean // Whether the todos were successfully updated
}
10. WebFetch Tool
Input:
{
url: string, // The URL to fetch content from
prompt: string // The prompt to run on the fetched content
}
Output:
{
bytes: number, // Size of the fetched content in bytes
code: number, // HTTP response code
codeText: string, // HTTP response code text
result: string, // Processed result from applying the prompt to the content
durationMs: number, // Time taken to fetch and process the content
url: string // The URL that was fetched
}
11. WebSearch Tool
Input:
{
query: string, // The search query to use
allowed_domains?: string[], // Only include results from these domains
blocked_domains?: string[] // Never include results from these domains
}
Output:
{
results: Array<{
title: string,
url: string,
snippet: string
}>
}
12. NotebookEdit Tool
Input:
{
notebook_path: string, // Absolute path to .ipynb file
cell_id?: string, // ID of cell to edit
new_source: string, // New source for the cell
cell_type?: "code" | "markdown", // Cell type (required for insert mode)
edit_mode?: "replace" | "insert" | "delete" // Default: "replace"
}
Output:
{
notebook: {
filePath: string,
cells: Array<{...}>
}
}
13. Task Tool (Agent)
Input:
{
description: string, // A short (3-5 word) description of the task
prompt: string, // The task for the agent to perform
subagent_type: string // The type of specialized agent to use
}
Output:
{
result: string // The agent's final report/result
}
14. ExitPlanMode Tool
Input:
{
plan: string // The plan to present to user for approval (supports markdown)
}
Output:
undefined // No output - this tool exits plan mode
15. KillShell Tool
Input:
{
shell_id: string // The ID of the background shell to kill
}
Output:
{
success: boolean // Whether the shell was successfully killed
}
16. ListMcpResources Tool
Input:
{
server?: string // Optional server name to filter resources by
}
Output:
{
resources: Array<{
uri: string,
name: string,
description?: string,
mimeType?: string,
server: string
}>
}
17. ReadMcpResource Tool
Input:
{
server: string, // The MCP server name
uri: string // The resource URI to read
}
Output:
{
contents: Array<{
uri: string,
mimeType?: string,
text?: string,
blob?: string // Base64-encoded binary data
}>
}
18. SlashCommand Tool
Input:
{
command: string // The slash command to execute with its arguments, e.g., "/review-pr 123"
}
Output:
{
success: boolean, // Whether the slash command is valid
commandName: string // The name of the slash command
}
Common Pattern: structuredPatch
Many file-editing tools return a structuredPatch array with this structure:
Array<{
oldStart: number, // Starting line number in old file
oldLines: number, // Number of lines in old file
newStart: number, // Starting line number in new file
newLines: number, // Number of lines in new file
lines: string[] // The actual diff lines
}>
Notes
-
No
successfield (mostly): Most built-in tools don't return asuccessboolean. The presence of the response indicates success; errors are thrown as exceptions. TodoWrite, KillShell, and SlashCommand are exceptions that do returnsuccess. - File paths: All file paths must be absolute, not relative.
-
User modifications: Some tools track whether the user modified Claude's proposed changes before accepting them (
userModifiedfield). - Structured diffs: File modification tools return structured diff information for tracking changes.
- Read tool variants: The Read tool returns different response structures based on file type (text, notebook, image, PDF).
-
Grep numMatches: The Grep tool includes an undocumented
numMatchesfield in its output that counts the total number of matches found. -
SlashCommand tool: This tool is used internally to execute custom slash commands defined in
.claude/commands/. It validates the command and returns whether it's valid along with the command name.
As typescript code
type HookEvent<Event extends string = string> = {
// Common fields
session_id: string;
transcript_path: string; // Path to conversation JSON
cwd: string;
hook_event_name: Event;
};
/**
* All built-in Claude Code tool names
*/
export type ClaudeCodeToolName =
| 'Edit'
| 'Write'
| 'Read'
| 'MultiEdit'
| 'Bash'
| 'BashOutput'
| 'KillShell'
| 'Glob'
| 'Grep'
| 'TodoWrite'
| 'WebFetch'
| 'WebSearch'
| 'NotebookEdit'
| 'Task'
| 'ExitPlanMode'
| 'ListMcpResources'
| 'ReadMcpResource'
| 'SlashCommand';
type Satisfies<Constraint, Target extends Constraint> = Target;
type ToolIOMapDef = {
[key in ClaudeCodeToolName]: {
input: {};
output: {} | undefined;
};
};
type ToolIOMap = Satisfies<
ToolIOMapDef,
{
Edit: {
input: {
/** The absolute path to the file to modify */
file_path: string;
/** The text to replace */
old_string: string;
/** The text to replace it with (must be different from old_string) */
new_string: string;
/** Replace all occurrences of old_string (default: false) */
replace_all?: boolean;
};
output: {
/** The file path that was edited */
filePath: string;
/** The original string that was replaced */
oldString: string;
/** The new string that replaced it */
newString: string;
/** The original file contents before editing */
originalFile: string;
/** Diff patch showing the changes */
structuredPatch: Array<{
/** Starting line number in old file */
oldStart: number;
/** Number of lines in old file */
oldLines: number;
/** Starting line number in new file */
newStart: number;
/** Number of lines in new file */
newLines: number;
/** The actual diff lines */
lines: string[];
}>;
/** Whether the user modified the proposed changes */
userModified: boolean;
/** Whether all occurrences were replaced */
replaceAll: boolean;
};
};
Write: {
input: {
/** The absolute path to the file to write (must be absolute, not relative) */
file_path: string;
/** The content to write to the file */
content: string;
};
output: {
/** Whether a new file was created or an existing file was updated */
type: 'create' | 'update';
/** The path to the file that was written */
filePath: string;
/** The content that was written to the file */
content: string;
/** Diff patch showing the changes (empty array for create) */
structuredPatch: Array<{
/** Starting line number in old file */
oldStart: number;
/** Number of lines in old file */
oldLines: number;
/** Starting line number in new file */
newStart: number;
/** Number of lines in new file */
newLines: number;
/** The actual diff lines */
lines: string[];
}>;
};
};
Read: {
input: {
/** The absolute path to the file to read */
file_path: string;
/** The line number to start reading from (only if file too large) */
offset?: number;
/** The number of lines to read (only if file too large) */
limit?: number;
};
output: {
/** File type: text, notebook, image, or pdf */
type: 'text' | 'notebook' | 'image' | 'pdf';
file: {
/** The path to the file that was read */
filePath: string;
/** The file contents (for text files) */
content?: string;
/** Number of lines returned (for text files) */
numLines?: number;
/** Starting line number, 1-based (for text files) */
startLine?: number;
/** Total lines in the file (for text files) */
totalLines?: number;
/** Base64-encoded data (for images and PDFs) */
base64?: string;
/** Notebook cells (for .ipynb files) */
cells?: Array<any>;
/** Original file size in bytes (for PDFs) */
originalSize?: number;
};
};
};
MultiEdit: {
input: {
/** The absolute path to the file to modify */
file_path: string;
/** Array of edit operations to perform sequentially (min 1 required) */
edits: Array<{
/** The text to replace */
old_string: string;
/** The text to replace it with */
new_string: string;
/** Replace all occurrences (default: false) */
replace_all?: boolean;
}>;
};
output: {
/** The file path that was edited */
filePath: string;
/** The original file contents before edits */
originalFileContents: string;
/** Array of diff hunks showing changes */
structuredPatch: Array<{
/** Starting line number in old file */
oldStart: number;
/** Number of lines in old file */
oldLines: number;
/** Starting line number in new file */
newStart: number;
/** Number of lines in new file */
newLines: number;
/** The actual diff lines */
lines: string[];
}>;
/** The edits that were applied */
edits: Array<{
old_string: string;
new_string: string;
replace_all: boolean;
}>;
/** Whether user modified the changes */
userModified: boolean;
};
};
Bash: {
input: {
/** The command to execute */
command: string;
/** Optional timeout in milliseconds (max 600000) */
timeout?: number;
/** Clear, concise description in 5-10 words */
description?: string;
/** Set to true to run in background */
run_in_background?: boolean;
};
output: {
/** Standard output */
stdout: string;
/** Standard error */
stderr: string;
/** AI-generated summary (if enabled) */
summary?: string;
/** Path to raw output file (if summarized) */
rawOutputPath?: string;
/** Whether the command was interrupted */
interrupted: boolean;
/** Whether output contains image data */
isImage: boolean;
/** Human-readable interpretation of return code */
returnCodeInterpretation?: string;
/** ID if run in background */
backgroundTaskId?: string;
/** Whether sandbox was overridden */
dangerouslyOverrideSandbox?: boolean;
};
};
BashOutput: {
input: {
/** The ID of the background shell to retrieve output from */
bash_id: string;
/** Optional regex to filter output lines */
filter?: string;
};
output: {
/** Standard output from background process */
stdout: string;
/** Standard error from background process */
stderr: string;
};
};
KillShell: {
input: {
/** The ID of the background shell to kill */
shell_id: string;
};
output: {
/** Whether the shell was successfully killed */
success: boolean;
};
};
Glob: {
input: {
/** The glob pattern to match files against */
pattern: string;
/** Directory to search in (defaults to cwd if omitted) */
path?: string;
};
output: {
/** Array of matching file paths */
filenames: string[];
/** Time taken to perform the search */
durationMs: number;
/** Number of files matched */
numFiles: number;
/** Whether results were truncated */
truncated: boolean;
};
};
Grep: {
input: {
/** Regex pattern to search for */
pattern: string;
/** File or directory to search in */
path?: string;
/** Glob pattern to filter files */
glob?: string;
/** Output mode (default: "files_with_matches") */
output_mode?: 'content' | 'files_with_matches' | 'count';
/** Lines before match (content mode only) */
'-B'?: number;
/** Lines after match (content mode only) */
'-A'?: number;
/** Lines before and after (content mode only) */
'-C'?: number;
/** Show line numbers (content mode only) */
'-n'?: boolean;
/** Case insensitive search */
'-i'?: boolean;
/** File type (js, py, rust, etc.) */
type?: string;
/** Limit output to first N entries */
head_limit?: number;
/** Enable multiline mode (default: false) */
multiline?: boolean;
};
output: {
/** Output mode used */
mode: 'content' | 'files_with_matches' | 'count';
/** Number of files with matches */
numFiles: number;
/** Array of filenames with matches */
filenames: string[];
/** Matched content (for content mode) */
content?: string;
/** Number of lines (for content mode) */
numLines?: number;
/** Number of matches found */
numMatches?: number;
};
};
TodoWrite: {
input: {
/** The updated todo list */
todos: Array<{
/** The todo description */
content: string;
/** Todo status */
status: 'pending' | 'in_progress' | 'completed';
/** Present continuous form (e.g., "Running tests") */
activeForm: string;
}>;
};
output: {
/** Whether the todos were successfully updated */
success: boolean;
};
};
WebFetch: {
input: {
/** The URL to fetch content from */
url: string;
/** The prompt to run on the fetched content */
prompt: string;
};
output: {
/** Size of the fetched content in bytes */
bytes: number;
/** HTTP response code */
code: number;
/** HTTP response code text */
codeText: string;
/** Processed result from applying the prompt to the content */
result: string;
/** Time taken to fetch and process the content */
durationMs: number;
/** The URL that was fetched */
url: string;
};
};
WebSearch: {
input: {
/** The search query to use */
query: string;
/** Only include results from these domains */
allowed_domains?: string[];
/** Never include results from these domains */
blocked_domains?: string[];
};
output: {
/** Search results */
results: Array<{
/** Result title */
title: string;
/** Result URL */
url: string;
/** Result snippet/description */
snippet: string;
}>;
};
};
NotebookEdit: {
input: {
/** Absolute path to .ipynb file */
notebook_path: string;
/** ID of cell to edit */
cell_id?: string;
/** New source for the cell */
new_source: string;
/** Cell type (required for insert mode) */
cell_type?: 'code' | 'markdown';
/** Edit mode (default: "replace") */
edit_mode?: 'replace' | 'insert' | 'delete';
};
output: {
/** The updated notebook */
notebook: {
/** Path to the notebook file */
filePath: string;
/** Notebook cells */
cells: Array<any>;
};
};
};
Task: {
input: {
/** A short (3-5 word) description of the task */
description: string;
/** The task for the agent to perform */
prompt: string;
/** The type of specialized agent to use */
subagent_type: string;
};
output: {
/** The agent's final report/result */
result: string;
};
};
ExitPlanMode: {
input: {
/** The plan to present to user for approval (supports markdown) */
plan: string;
};
/** No output - this tool exits plan mode */
output: undefined;
};
ListMcpResources: {
input: {
/** Optional server name to filter resources by */
server?: string;
};
output: {
/** Available MCP resources */
resources: Array<{
/** Resource URI */
uri: string;
/** Resource name */
name: string;
/** Resource description */
description?: string;
/** MIME type */
mimeType?: string;
/** MCP server name */
server: string;
}>;
};
};
ReadMcpResource: {
input: {
/** The MCP server name */
server: string;
/** The resource URI to read */
uri: string;
};
output: {
/** Resource contents */
contents: Array<{
/** Resource URI */
uri: string;
/** MIME type */
mimeType?: string;
/** Text content */
text?: string;
/** Base64-encoded binary data */
blob?: string;
}>;
};
};
SlashCommand: {
input: {
/** The slash command to execute with its arguments (e.g., "/review-pr 123") */
command: string;
};
output: {
/** Whether the slash command is valid */
success: boolean;
/** The name of the slash command */
commandName: string;
};
};
}
>;
export type PreToolEvent<Name extends ClaudeCodeToolName> =
HookEvent<'PreToolUse'> & {
tool_name: Name;
tool_input: ToolIOMap[Name]['input'];
};
export type PostToolEvent<Name extends ClaudeCodeToolName> =
HookEvent<'PostToolUse'> & {
tool_name: Name;
tool_input: ToolIOMap[Name]['input'];
tool_response: ToolIOMap[Name]['output'];
};
export type HookOutput = {
continue?: boolean; // Whether Claude should continue after hook execution (default: true)
stopReason?: string; // Message shown when continue is false
suppressOutput?: boolean; // Hide stdout from transcript mode (default: false)
systemMessage?: string; // Optional warning message shown to the user
decision?: 'block' | undefined;
reason?: string; // reason for decision.
hookSpecificOutput?: {
hookEventName?: string;
additionalContext?: string;
};
};
export type PreToolUseHookOutput<
ToolName extends ClaudeCodeToolName = ClaudeCodeToolName,
> = HookOutput & {
hookSpecificOutput?: {
hookEventName: 'PreToolUse';
permissionDecision?: 'allow' | 'deny' | 'ask';
permissionDecisionReason?: string;
updatedInput?: ToolIOMap[ToolName]['input'];
};
};
This issue has been inactive for 30 days. If the issue is still occurring, please comment to let us know. Otherwise, this issue will be automatically closed in 30 days for housekeeping purposes.