{"id":168731,"date":"2026-06-14T02:14:30","date_gmt":"2026-06-13T23:14:30","guid":{"rendered":"https:\/\/computingforgeeks.com\/?p=168731"},"modified":"2026-06-14T02:14:30","modified_gmt":"2026-06-13T23:14:30","slug":"claude-code-hooks-guide","status":"publish","type":"post","link":"https:\/\/computingforgeeks.com\/claude-code-hooks-guide\/","title":{"rendered":"Claude Code Hooks: The Complete Guide"},"content":{"rendered":"<p>Before you wire up a single hook, the question worth settling is what you actually want them for. A prompt in your CLAUDE.md asking Claude to run the linter after edits is a suggestion the model can forget. A hook that runs the linter after every edit is a guarantee, because Claude Code runs it, not the model. That difference is the whole point of hooks: they turn &#8220;please remember to&#8221; into deterministic behavior that fires on its own, every time, whether the model cooperates or not.<\/p>\n\n<p>This guide covers the full Claude Code hooks system: the roughly thirty lifecycle events you can attach to, the exact <code>settings.json<\/code> shape, how matchers and the <code>if<\/code> filter decide when a hook runs, the exit-code and JSON contracts a hook uses to allow or block an action, the five handler types, and a hook we built and ran that blocks <code>rm -rf<\/code> even with permissions turned off. Everything here was run on Claude Code 2.1.177 in June 2026, so the commands and the block you will see further down are real.<\/p>\n\n<h2>What a Claude Code hook is, and why it is a guarantee<\/h2>\n\n<p>A hook is a command, HTTP call, or agent that Claude Code triggers at a defined point in its lifecycle. When that point arrives, Claude Code feeds the hook a JSON description of what is happening on standard input, runs it, and reads the result. Three powers come out of that simple loop. A hook can <strong>observe<\/strong> what Claude is about to do or has already done, it can <strong>inject<\/strong> extra context into the conversation, and on the events that support it, it can <strong>block<\/strong> the action outright.<\/p>\n\n<p>The architectural detail that matters: hooks are enforced by Claude Code itself, not by the model. Permission rules and prompts shape what Claude tries to do, but a hook decides what is allowed to actually happen. In practice this means a hook is the right tool whenever you need a hard boundary, like keeping secrets out of reach or stopping a destructive command, rather than a soft nudge you hope the model honors.<\/p>\n\n<h2>The events you can hook into<\/h2>\n\n<p>This is where most write-ups are out of date. A guide that tells you there are eight or twelve hook events is describing Claude Code as it stood in mid-2025. The current set is roughly thirty events and still growing, with recent additions like <code>MessageDisplay<\/code> (which can transform assistant text as it is shown) and the <code>terminalSequence<\/code> output for desktop notifications both landing in the 2.1.x line. You do not need all of them, but knowing the full surface is what lets you pick the right one instead of forcing everything through <code>PostToolUse<\/code>.<\/p>\n\n<p>Grouped by what they watch, the events break down like this:<\/p>\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Category<\/th><th>Events<\/th><th>What they fire on<\/th><\/tr><\/thead><tbody>\n<tr><td>Tool lifecycle<\/td><td><code>PreToolUse<\/code>, <code>PostToolUse<\/code>, <code>PostToolUseFailure<\/code>, <code>PostToolBatch<\/code>, <code>PermissionRequest<\/code>, <code>PermissionDenied<\/code><\/td><td>Before, after, or around a tool call and its permission decision<\/td><\/tr>\n<tr><td>Prompt and turn<\/td><td><code>UserPromptSubmit<\/code>, <code>UserPromptExpansion<\/code>, <code>Stop<\/code>, <code>StopFailure<\/code>, <code>MessageDisplay<\/code>, <code>Notification<\/code><\/td><td>When you submit a prompt, when Claude finishes, when text is shown<\/td><\/tr>\n<tr><td>Session<\/td><td><code>SessionStart<\/code>, <code>SessionEnd<\/code>, <code>Setup<\/code>, <code>InstructionsLoaded<\/code>, <code>ConfigChange<\/code>, <code>CwdChanged<\/code>, <code>FileChanged<\/code><\/td><td>Session start and end, config and file changes, CLAUDE.md loads<\/td><\/tr>\n<tr><td>Subagents and tasks<\/td><td><code>SubagentStart<\/code>, <code>SubagentStop<\/code>, <code>TaskCreated<\/code>, <code>TaskCompleted<\/code>, <code>TeammateIdle<\/code><\/td><td>When a subagent or task starts, finishes, or a teammate goes idle<\/td><\/tr>\n<tr><td>Compaction and worktrees<\/td><td><code>PreCompact<\/code>, <code>PostCompact<\/code>, <code>WorktreeCreate<\/code>, <code>WorktreeRemove<\/code><\/td><td>Around context compaction and git worktree isolation<\/td><\/tr>\n<tr><td>MCP elicitation<\/td><td><code>Elicitation<\/code>, <code>ElicitationResult<\/code><\/td><td>When an MCP server asks the user for input mid-tool-call<\/td><\/tr>\n<\/tbody><\/table><\/figure>\n\n\n<p>In day-to-day use, a handful carries almost all the weight: <code>PreToolUse<\/code> and <code>PostToolUse<\/code> for acting around tool calls, <code>UserPromptSubmit<\/code> for shaping what reaches the model, <code>SessionStart<\/code> for loading context, and <code>Stop<\/code> for end-of-turn work. The rest of the catalog is there for the day you need it. The <a href=\"https:\/\/computingforgeeks.com\/claude-code-subagents-guide\/\">subagent events<\/a> pair naturally with custom agents, and the elicitation events only matter once you are running <a href=\"https:\/\/computingforgeeks.com\/claude-code-mcp-servers-setup\/\">MCP servers<\/a> that prompt for input.<\/p>\n\n<h2>Your first hook in settings.json<\/h2>\n\n<p>Hooks live in your settings files, and Claude Code reads three of them in order: <code>~\/.claude\/settings.json<\/code> for your personal hooks across every project, <code>.claude\/settings.json<\/code> in a repo for hooks the whole team shares, and <code>.claude\/settings.local.json<\/code> for project hooks you keep out of version control. They sit alongside the rest of your <a href=\"https:\/\/computingforgeeks.com\/claude-code-dot-claude-directory-guide\/\">project Claude Code config<\/a>. Open the shared project file:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>vim .claude\/settings.json<\/code><\/pre>\n\n\n<p>The structure nests three levels deep: the <code>hooks<\/code> key, then the event name, then an array of matcher groups, each holding the actual hooks to run. A minimal example that echoes a line every time Claude edits or writes a file:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>{\n  \"hooks\": {\n    \"PostToolUse\": [\n      {\n        \"matcher\": \"Edit|Write\",\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \"echo \\\"Claude touched a file at $(date)\\\" >> ~\/claude-edits.log\"\n          }\n        ]\n      }\n    ]\n  }\n}<\/code><\/pre>\n\n\n<p>One operational note that saves confusion: edits to a hook in your settings files are picked up automatically by Claude Code&#8217;s file watcher, so a change usually takes effect without restarting the session. The <code>\/hooks<\/code> menu is a read-only view of what is currently wired up, handy for confirming a hook registered, but you make changes by editing the JSON, not from that menu. If you ever need to switch every hook off at once, set <code>\"disableAllHooks\": true<\/code> in settings rather than deleting them.<\/p>\n\n<h2>Matching the right events<\/h2>\n\n<p>The <code>matcher<\/code> field decides which occurrences of an event a hook group responds to, and how it is read depends entirely on the characters inside it. A value of <code>*<\/code>, an empty string, or an omitted matcher fires on every occurrence. A value containing only letters, digits, underscores, and the pipe is treated as an exact string or a pipe-separated list of exact strings, so <code>Bash<\/code> matches only the Bash tool and <code>Edit|Write<\/code> matches either one. Anything with another character in it becomes a JavaScript regular expression.<\/p>\n\n<p>That last rule is the source of a real gotcha with MCP tools. MCP tool names follow the pattern <code>mcp__&lt;server&gt;__&lt;tool&gt;<\/code>, and to match every tool from one server you have to append <code>.*<\/code>, because a bare <code>mcp__memory<\/code> contains only letters and underscores and is therefore compared as an exact string that matches nothing:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>\"matcher\": \"mcp__memory__.*\"<\/code><\/pre>\n\n\n<p>For finer control on tool events, there is a second filter. The <code>if<\/code> field takes permission-rule syntax and runs the hook only when the tool call matches, so you can scope a hook to a narrow case without writing the matching logic yourself:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>\"if\": \"Bash(git push *)\"<\/code><\/pre>\n\n\n<p>The catch worth remembering is that <code>if<\/code> is only evaluated on the tool events, namely <code>PreToolUse<\/code>, <code>PostToolUse<\/code>, <code>PostToolUseFailure<\/code>, <code>PermissionRequest<\/code>, and <code>PermissionDenied<\/code>. Put it on any other event and the hook never runs.<\/p>\n\n<h2>What your hook receives, and how it answers<\/h2>\n\n<p>Every hook gets a JSON object on standard input. The common fields are present on every event: <code>session_id<\/code>, <code>transcript_path<\/code>, <code>cwd<\/code>, <code>permission_mode<\/code>, and <code>hook_event_name<\/code>. Tool events add the ones you usually care about, <code>tool_name<\/code> and <code>tool_input<\/code>; events firing in a tool-use context also carry <code>effort<\/code> (an object whose <code>level<\/code> field holds the active effort), and hooks firing inside a subagent carry <code>agent_id<\/code> and <code>agent_type<\/code>. A <code>PreToolUse<\/code> payload for a Bash call looks like this:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>{\n  \"session_id\": \"abc123\",\n  \"cwd\": \"\/home\/user\/my-project\",\n  \"permission_mode\": \"default\",\n  \"hook_event_name\": \"PreToolUse\",\n  \"tool_name\": \"Bash\",\n  \"tool_input\": { \"command\": \"rm -rf \/tmp\/build\" }\n}<\/code><\/pre>\n\n\n<p>The hook answers in one of two ways, and the simplest is the exit code. Exit <code>0<\/code> means success, and Claude Code parses standard out for any JSON instructions. Exit <code>2<\/code> is the blocking signal: Claude Code discards stdout and feeds your stderr text back to the model as the reason the action was stopped. Every other exit code is treated as a non-blocking error, the action proceeds, and a notice shows in the transcript. The trap that catches people is that exit <code>1<\/code>, the conventional Unix failure code, does <strong>not<\/strong> block. If a hook is meant to enforce a policy, it has to exit <code>2<\/code>.<\/p>\n\n<p>There is one more useful behavior tied to standard out. For most events stdout only goes to the debug log, but for <code>UserPromptSubmit<\/code>, <code>UserPromptExpansion<\/code>, and <code>SessionStart<\/code> it is added straight into the context as something Claude can read and act on. That is the mechanism behind context-injection hooks, where a script prints the current ticket number or git branch and Claude picks it up without you typing it.<\/p>\n\n<h2>Telling Claude exactly what to do<\/h2>\n\n<p>Exit codes are blunt. For precise control, a hook exits <code>0<\/code> and prints JSON, and this is the layer most guides skip. A set of universal fields works on any event: <code>continue<\/code> to halt all processing (with <code>stopReason<\/code> as the message shown when it does), <code>systemMessage<\/code> to surface a warning to the user, <code>suppressOutput<\/code> to hide the hook&#8217;s stdout, and <code>terminalSequence<\/code> to emit a desktop notification or set the window title.<\/p>\n\n<p>Beyond those, the decision shape differs by event, which is the nuance worth getting right. Most events that can steer Claude use a top-level <code>decision<\/code>, for example <code>UserPromptSubmit<\/code>, <code>PostToolUse<\/code>, and <code>Stop<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>{ \"decision\": \"block\", \"reason\": \"Commit message is missing a ticket reference.\" }<\/code><\/pre>\n\n\n<p><code>PreToolUse<\/code> is the exception and the one you will reach for most, because it carries a real permission decision rather than a yes or no. It returns a <code>hookSpecificOutput<\/code> with a <code>permissionDecision<\/code> of <code>allow<\/code>, <code>deny<\/code>, <code>ask<\/code>, or <code>defer<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>{\n  \"hookSpecificOutput\": {\n    \"hookEventName\": \"PreToolUse\",\n    \"permissionDecision\": \"deny\",\n    \"permissionDecisionReason\": \"Destructive command blocked by hook\"\n  }\n}<\/code><\/pre>\n\n\n<p>The difference matters in practice. <code>allow<\/code> skips the normal permission prompt entirely, <code>ask<\/code> forces one, and <code>deny<\/code> stops the call with your reason handed to the model. We would reach for the JSON form over a bare exit <code>2<\/code> whenever the reason needs to be specific enough for Claude to act on, and for the cheap &#8220;block anything that looks like X&#8221; guards, exit <code>2<\/code> with a stderr message is less code.<\/p>\n\n<h2>Five kinds of hook handler<\/h2>\n\n<p>The <code>type<\/code> field is not limited to shell commands, though that is where almost everyone starts. There are five handler types, and the two that older guides miss are exactly the ones that open up the interesting designs:<\/p>\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Type<\/th><th>What it does<\/th><th>Reach for it when<\/th><\/tr><\/thead><tbody>\n<tr><td><code>command<\/code><\/td><td>Runs a shell command with the event JSON on stdin<\/td><td>The default, for scripts and CLI tools<\/td><\/tr>\n<tr><td><code>http<\/code><\/td><td>POSTs the event JSON to a URL<\/td><td>Centralized logging or a policy service<\/td><\/tr>\n<tr><td><code>mcp_tool<\/code><\/td><td>Calls a tool on a connected MCP server<\/td><td>You already run an MCP server that can act<\/td><\/tr>\n<tr><td><code>prompt<\/code><\/td><td>Sends a single-turn prompt to a model for a verdict<\/td><td>The check needs judgment, not a regex<\/td><\/tr>\n<tr><td><code>agent<\/code><\/td><td>Spawns a subagent with tools like Read and Grep<\/td><td>The check needs to investigate the codebase<\/td><\/tr>\n<\/tbody><\/table><\/figure>\n\n\n<p>The <code>prompt<\/code> and <code>agent<\/code> handlers are the genuinely new design space. A <code>prompt<\/code> hook on <code>UserPromptSubmit<\/code> can have a cheap model judge whether a request looks risky before the main model ever sees it, and an <code>agent<\/code> hook can actually go read files to make its decision. Most production setups still lean on <code>command<\/code>, because a shell script is predictable and free, but the option to escalate to a model is there when the rule cannot be expressed as a pattern.<\/p>\n\n<h2>A hook that blocks a destructive command<\/h2>\n\n<p>Theory settles into place once a hook stops something real. Here is a <code>PreToolUse<\/code> hook scoped to the Bash tool, wired into <code>.claude\/settings.json<\/code> and pointed at a small script. The script reads the proposed command off stdin and exits <code>2<\/code> if it spots a recursive force-delete:<\/p>\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"2560\" height=\"1294\" src=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-claude-code-hooks-pretooluse-settings-json.png\" alt=\"A Claude Code PreToolUse hook wired in settings.json calling a shell script that blocks rm -rf\" class=\"wp-image-168727\" title=\"\" srcset=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-claude-code-hooks-pretooluse-settings-json.png 2560w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-claude-code-hooks-pretooluse-settings-json-300x152.png 300w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-claude-code-hooks-pretooluse-settings-json-1024x518.png 1024w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-claude-code-hooks-pretooluse-settings-json-768x388.png 768w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-claude-code-hooks-pretooluse-settings-json-1536x776.png 1536w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-claude-code-hooks-pretooluse-settings-json-2048x1035.png 2048w\" sizes=\"auto, (max-width: 2560px) 100vw, 2560px\" \/><\/figure>\n\n\n<p>To make the point unmissable, we ran Claude with <code>--dangerously-skip-permissions<\/code>, which turns off the permission system entirely, then asked it to delete a directory with <code>rm -rf<\/code>. With permissions off, the hook is the only thing standing in the way. It held: the command was blocked, the stderr reason went back to the model, and the <code>build<\/code> directory was still there afterward.<\/p>\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"2560\" height=\"694\" src=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-claude-code-hooks-block-dangerous-command.png\" alt=\"A Claude Code PreToolUse hook blocks rm -rf even with permissions skipped and the build directory survives\" class=\"wp-image-168728\" title=\"\" srcset=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-claude-code-hooks-block-dangerous-command.png 2560w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-claude-code-hooks-block-dangerous-command-300x81.png 300w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-claude-code-hooks-block-dangerous-command-1024x278.png 1024w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-claude-code-hooks-block-dangerous-command-768x208.png 768w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-claude-code-hooks-block-dangerous-command-1536x416.png 1536w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-claude-code-hooks-block-dangerous-command-2048x555.png 2048w\" sizes=\"auto, (max-width: 2560px) 100vw, 2560px\" \/><\/figure>\n\n\n<p>One honest lesson came out of testing that is worth more than the success itself. On an earlier run, the hook blocked <code>rm -rf build<\/code>, and Claude deleted the file explicitly and then ran <code>rmdir<\/code>, a path the regex did not catch. The takeaway shapes how you write these: scope a guard to the behavior you fear, not to one spelling of one command, and treat hooks as a layer alongside the permission system rather than a replacement for it. A hook that only knows <code>rm -rf<\/code> is a speed bump, not a wall.<\/p>\n\n<h2>More patterns worth wiring in<\/h2>\n\n<p>The same machinery covers most of what teams actually automate. Auto-formatting on edit is the canonical <code>PostToolUse<\/code> hook, matched to the file-editing tools so a formatter runs the moment Claude changes code:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>{\n  \"hooks\": {\n    \"PostToolUse\": [\n      {\n        \"matcher\": \"Edit|Write\",\n        \"hooks\": [\n          { \"type\": \"command\", \"command\": \"$CLAUDE_PROJECT_DIR\/.claude\/hooks\/format-changed.sh\" }\n        ]\n      }\n    ]\n  }\n}<\/code><\/pre>\n\n\n<p>Context injection is the <code>UserPromptSubmit<\/code> pattern, where a script prints something useful and Claude reads it because stdout on that event becomes context. A script that echoes the current git branch and open ticket, for instance, means you stop pasting that detail into every prompt. And for ambient awareness, a <code>Notification<\/code> hook that emits a <code>terminalSequence<\/code> fires a desktop alert when Claude needs your attention, so a long run does not sit waiting silently while you have switched windows.<\/p>\n\n<h2>How we run hooks in production<\/h2>\n\n<p>We lean on this system hard for our own publishing pipeline, and the shape is worth sharing because it is the pattern most worth copying. Every article on this site goes through a publish step, and a <code>PreToolUse<\/code> hook on the Bash tool inspects that command before it runs. If the command is a publish and the article has not passed its checks, missing meta, an unverified review, a too-short body, the hook exits <code>2<\/code> and the publish never happens. The operator sees exactly why and fixes it.<\/p>\n\n<p>The trade-off we made there is the one to think through for any enforcement hook. A deterministic gate is stricter than trusting whoever is at the keyboard, and that strictness is the entire value: it means a rule cannot be skipped on a tired afternoon. The cost is that the gate has to be correct, because a buggy hook blocks legitimate work as reliably as it blocks mistakes. We keep ours narrow and loud, each one checking a single condition and printing a precise reason, which is the same discipline that keeps a subagent or a CI check maintainable. For teams already running Claude Code in their <a href=\"https:\/\/computingforgeeks.com\/claude-code-github-actions-infrastructure\/\">CI and infrastructure<\/a> workflows, hooks are the local equivalent of a pre-commit gate.<\/p>\n\n<h2>Ready-made hooks from the community<\/h2>\n\n<p>You do not have to write everything yourself. A few collections are worth raiding for working scripts (star counts approximate, as of June 2026):<\/p>\n\n\n<ul class=\"wp-block-list\">\n<li><code>disler\/claude-code-hooks-mastery<\/code> (~4K) is the canonical teaching repo, with a standalone script per event and a logging hook for every one. It is the best place to see the full surface in action, though it ships without a clear license, so read before you reuse.<\/li>\n<li><code>carlrannaberg\/claudekit<\/code> (~700, MIT) is a production toolkit of typecheck, lint, and test-on-change hooks plus a git checkpoint on stop.<\/li>\n<li><code>nizos\/tdd-guard<\/code> (~2K, MIT) enforces test-driven development by blocking implementation before a failing test exists.<\/li>\n<li><code>hesreallyhim\/awesome-claude-code<\/code> (~46K) is the umbrella list and the hub to track the rest of the ecosystem.<\/li>\n<\/ul>\n\n\n<p>Treat anything you copy the way you would treat any script that runs with your privileges, because that is exactly what a hook is.<\/p>\n\n<h2>Running hooks safely in production<\/h2>\n\n<p>That last point is the one to close on, because it is where hooks differ from the rest of your Claude Code config. A hook is arbitrary code that Claude Code executes automatically with your full permissions, so the security posture has to match. A short checklist keeps it honest: read every hook before you trust it, and be especially careful with project hooks from a repo you cloned. Reference scripts through <code>$CLAUDE_PROJECT_DIR<\/code> rather than absolute paths so they stay portable. Remember that a personal <code>~\/.claude\/settings.json<\/code> hook follows you into every project, including ones you did not write. Give long-running hooks a sensible <code>timeout<\/code> so a hung script does not stall the session. And keep the exit-code contract in muscle memory, since an enforcement hook that exits <code>1<\/code> instead of <code>2<\/code> looks like it is working while quietly letting everything through.<\/p>\n\n<p>Get those habits right and hooks become the most reliable part of your setup, the layer that does not depend on the model remembering anything. Pin the events table next to your <a href=\"https:\/\/computingforgeeks.com\/claude-code-cheat-sheet\/\">Claude Code cheat sheet<\/a>, start with one <code>PostToolUse<\/code> formatter and one <code>PreToolUse<\/code> guard, and grow the set as you find the seams in your own workflow where a guarantee beats a suggestion.<\/p>","protected":false},"excerpt":{"rendered":"<p>Before you wire up a single hook, the question worth settling is what you actually want them for. A prompt in your CLAUDE.md asking Claude to run the linter after edits is a suggestion the model can forget. A hook that runs the linter after every edit is a guarantee, because Claude Code runs it, &#8230; <a title=\"Claude Code Hooks: The Complete Guide\" class=\"read-more\" href=\"https:\/\/computingforgeeks.com\/claude-code-hooks-guide\/\" aria-label=\"Read more about Claude Code Hooks: The Complete Guide\">Read more<\/a><\/p>\n","protected":false},"author":17,"featured_media":168732,"comment_status":"open","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[39034,690,299],"tags":[17245,212,669,211],"cfg_series":[39880],"class_list":["post-168731","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ai","category-dev","category-how-to","tag-ai","tag-automation","tag-dev","tag-devops","cfg_series-claude-code-mastery"],"_links":{"self":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts\/168731","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/users\/17"}],"replies":[{"embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/comments?post=168731"}],"version-history":[{"count":2,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts\/168731\/revisions"}],"predecessor-version":[{"id":168735,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts\/168731\/revisions\/168735"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/media\/168732"}],"wp:attachment":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/media?parent=168731"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/categories?post=168731"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/tags?post=168731"},{"taxonomy":"cfg_series","embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/cfg_series?post=168731"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}