AI

Hooks That Pay for Themselves

A session-start hook runs once, costs nothing, and gives your agent context it would otherwise ask for or guess wrong. The highest-leverage 10 lines you'll write this week.

Hooks That Pay for Themselves
Podcast18:35
Read transcript
# Transcript — ai-coding-setup ep 03 _Auto-generated with mlx-whisper (mlx-community/whisper-base.en-mlx). Lightly readable; not edited._ You know, you are probably leaking like 15 minutes of focus time every single day to your AI coding agent. Oh, at least 15 minutes easily. Right. And it is not because the underlying model is deficient or anything. It's just because it has absolutely zero situational awareness. Yeah, it starts completely blank. Exactly. You fire up a fresh terminal session and the agent, uh, it immediately starts aggressively modifying files for a database migration that you already shipped on Tuesday. Yeah. Or even worse, it just sits there with a blinking cursor and it basically forces you to type out this manual tedious recap of your entire working tree before you can write a single useful line of code, which is just a massive compounding leak of recoverable time. Cool. So today's source material is this really interesting analysis titled hooks that pay for themselves and we are doing a deep dive into how to configure cloud code lifecycle events to mechanically inject perfect context into the agent. The literal second decision starts bypassing that whole orientation phase entirely. Exactly. And let's set the baseline right now for everyone listening. There is zero hype in this discussion today. None. We are keeping it super dry and strictly practical. We just want to look at the granular mechanics of eliminating that daily orientation tax. Yeah. Because I mean, those numbers add up way faster than most engineering teams realize. Oh, the cost of the status quo is highly measurable. I mean, think about every time you open a terminal and have to manually establish state, right typing out, Hey, I'm currently working on the new offflow. Please look at my recent commits in these specific files. Exactly. That operation takes what? Two, three minutes? At least. Right. So multiply that across maybe five sessions a day and then across an entire mid-sized engineering team. You are burning hundreds of hours a month, just playing catch up with an intelligence that technically speaking has read access to your entire repository. But it just lacks that human intuition to know where to look. Precisely. It doesn't know what you care about right now. So the solution, the source outlines centers entirely around hooks. We are basically talking about basic shell scripts that execute automatically on four distinct lifecycle events within the agent's workflow. Right. Yeah. The events map directly to the execution loop. So you have session start, which runs the moment you initialize the agent, then pre tool use, which fires just before the agent executes an action against your system, like writing a file or running a bash command. Yep. Then post tool use triggers immediately after that tool returns its payload. And finally, you have stop, which executes when the agent finishes its whole response generation and hands control back to you. Okay. But the critical mechanism here is data piping, right? Because the output of these shell scripts doesn't actually require any specialized parsing. No, not. Whatever your script writes to standard out is simply injected into the agent system prompt as plaintext. Exactly. The model just reads it the same way it reads an environment variable dump. I like to think of the agent like a highly capable junior developer who suffers from severe amnesia every single time they step away from the keyboard. That is a great analogy. Right. Because instead of sitting them down to orally re-explain the state of the repository every single morning, you just hand them an automatic briefing document as they walk in the door. Yeah. And setting up that automated handoff just requires establishing where the configuration lives. So project scope hooks belong in a committed team file inside the repository. That's the dot cloud slash settings dot JSON file, right? Yep. That ensures the whole team shares the context parameters. But if you have like personal, machine wide workflow tweaks, say a specific CLI tool only you use, those belong in a global settings file in your user route directory. OK, wait, but executing a shell script every single time the agent touches a tool sounds like a really fast way to pollute the context window and burn through tokens. Yeah. Like if I have a script checking file formatting, I absolutely do not need it executing when the agent is just gripping a directory for a variable name. Right. Which is why the JSON configuration handles that filtering through a matcher field. OK. It uses standard glob patterns to constrain the trigger. So you can figure the matcher with a string like asterisk, right? Asterisk or asterisk of bash asterisk. So the engine evaluates that pattern against the tool name the agent is trying to call. Exactly. And if it doesn't match, the hook is entirely ignored. So the signal to noise ratio stays high and you aren't spamming the session with irrelevant standard out data. That makes a lot of sense. So since session start is the event that generates that initial breaching document we talked about, let's look at the actual implementation. The source actually describes this as the highest leverage 10 lines of code a developer can write. Yeah, I would agree with that. So what specifically is going into standard out to stop the agent from hallucinating state right out of the gate? Well, the absolute first priority is get context. A standard session start script should really just run get ref parse to print out the current branch name. Super simple. Right. Followed by get log dash and three to show the most recent commit messages. And then a quick get status dash dash short. So zero complex configuration zero, but it instantly angers the model to the reality of the repository. The agent immediately knows what state the local working directory is in. Rather than trying to infer your goals based on whatever random file it touched last. Exactly. Now, the next item on the sources list is outputting the current date, which honestly initially feels incredibly trivial. It does seem so. Right. Like we're using state of the art LMS and we have to write a bash command to echo the calendar. But if you think about the underlying architecture, it makes perfect sense. Because of the training data cut off. Exactly. The agent has a hard training data cut off. If you ask it to update a change log or evaluate library deprecation warnings or write seasonal logic without explicitly giving it today's timestamp, it has to rely on probabilistic guessing. And it will confidently insert a date from 18 months ago, but injecting the date takes less than a millisecond of execution time and it consumes what maybe four tokens of context space. There we anything. Right. But it heavily anchors the model's knowledge retrieval mechanism. And beyond the date and the get status, the third piece of this start hook is cadding a local human written context file right into the prompt. OK, the source suggests using a dedicated file for this, like dot clawed slash context dot MD. But we need to clearly differentiate this from the standard AI coding paradigm. Yes. Because we're not talking about another global clay ad dot MD or cursor rules file where a team dictates formatting standards. Right. A rules file represents permanent infrastructure. This context dot MD file is strictly transient by design. It acts as a digital sticky note about what is currently in flight at this exact moment, like a developer might just jock down migrating user schema to handle multi tenant routing. Database layer is complete, focusing entirely on front end state updates. Exactly. So when the session initializes, the agent reads that sticky note and skips the entire discovery phase. It doesn't have to scan your uncommitted changes to guess what you were trying to achieve. That's brilliant. And for engineers working out of rigorous issue trackers, you could actually swap the static markdown file for a dynamic query. Oh, yeah. If you're using the GitHub CLI, your session start hook could just run g issue list dash dash assignee at me and print your immediate backlog. Yeah. So the agent gets a prioritized structured list of tickets without you copying and pasting URLs into the chat window. But that integration is only powerful if you build in structural resilience. That is a crucial point. Whenever a hook calls a third party CLI, you have to design the script to fail gracefully. Right. Because if a teammate pulls down your team settings, Json file, but they don't have the GitHub CLI installed locally, the script is going to throw an unhandled exception, which will completely block the AI session from initializing. It breaks their workflow before it even begins. So the defensive implementation there is just basic shell scripting. You pen the pipe, pipe true operator to your bash commands or write a quick wrapper script that deliberately suppresses standard error. Exactly. You instruct the hook to attempt the task tracker query. And if the binary isn't found or the network times out, it just exits quietly with a status code of zero. The session starts without the ticket data. But the important thing is it still starts resilience has to be the default state for these configs. Okay. So session start gets the agent anchored. But once the actual typing starts syntax errors and hallucinated APIs are basically inevitable. And fortunately, yes, the source outlines how to use the remaining lifecycle events, pre tool use and post tool use to build an automated real time guardrails system. So let's look at that pre execution phase first. Well, pre tool use functions exceptionally well for validating conventions before the file system is actually modified. Give me an example. So if your engineering org enforces strict file naming, like say, react components must be Pascal case or custom hooks must begin with the word use. You can configure a script to parse the proposed file path the agent is attempting to write to. Oh, I see. If the script detects or rejects violation, it outputs a warning string. The agent reads that standard out, pauses its action and corrects the path before the right operation even executes. Interesting. But that operates more like a soft reminder rather than a hard security gate, right? If I really want to protect the main branch from bad conventions, I'm still running a traditional git pre commit hook or enforcing it in CI. Oh, absolutely. You still need repository level enforcement. The pre tool use hook exists solely to provide the agent with immediate awareness during the drafting phase. So it prevents the model from generating 50 lines of code under an incorrect assumption. Right. Which you would then have to manually prompt it to refactor later. It basically correct the steering wheel before the car hits the rumble strip. But the execution gets far more aggressive on the other side of the action. The post tool use hook triggers the exact moment the file right completes. Yeah, this is where it gets really fun. The source highlights using this event to execute a TypeScript compiler check. And I like to use a data piping analogy here to visualize this. It is essentially attaching a stethoscope directly to your compiler. That's a great way to put it. Right. The exact moment a type error occurs, the AI hears the heartbeat skip instantly. You don't have to print out the EKG trace, copy it and hand it across the desk. But the technical implementation of that compiler check dictates whether it succeeds or fails. You absolutely do not want to trigger a full build process on every single file safe. Oh, that would be painfully slow. So you configure the hook to execute a command like TSC dash dash no emit. This runs the type validation deliberately skips out putting the compiled JavaScript files and simply pipes the raw diagnostic errors straight to standard out. And the agent processes those errors seamlessly as part of the tools return payload. Which if you think about it, fundamentally inverts the standard workflow. Normally the AI generates a block of code, tells me it's finished, and I switch over to my terminal to run npm run build. And your console immediately bleeds red with type mismatches. Yes. And then I have to copy the entire trace, paste it back into the AI chat window and wait for it to generate a fix. Right. But with a post tool use hook running TEC dash dash no emit. The agent triggers the check itself, reads its own error trace. And initiates effects before I am even aware it made a mistake. The feedback loop closes entirely without human mediation. The agent iteratively corrects its own output until standard out returns clean. That is wild. And you can apply the exact same pattern to linting and formatting. Configure a post use hook to trigger Slin dash dash fix or prettier. Oh, so the agent never has to waste tokens or inference compute generating perfectly spaced JSON or formatting JSX tags. The shell script handles the standardization natively, keeping your git diffs immaculate while the model just focuses on the actual logic. OK, so the syntax errors are caught. The code is properly formatted and the feature is done. But three months from now, another engineer is going to review this implementation and wonder why on earth the AI chose a highly specific slightly bizarre design pattern, which happens all the time. Does this framework offer any way to maintain a forensic trail of what the model was thinking? Yes. The fourth lifecycle event stop handles that exact logging requirement. OK, stop hooks execute when the agent completely finishes its response cycle and is idling. So you can configure a simple bash script to grab the context summary of the session and append it to a local markdown log file. Oh, creating a searchable audit trail. Exactly. A highly searchable lightweight audit trail of the agent's decision making state and context window at that specific time step. Now, this architecture sounds highly efficient on paper, but production environments are inherently messy and developer workflows are fragile. Very fragile. So what is the catch here? If I deploy this configuration across my team repository tomorrow, what is the fastest way an engineer accidentally nukes their own workflow? Well, the single biggest vulnerability in this system is synchronous execution. Claude code pauses all operations and waits for your shell script to exit before proceeding. Every millisecond your hook takes to run is a millisecond of artificial latency injected directly into the agent's processing loop. So if you configure a heavy ESLint configuration or a massive test suite to run on every single file, write via post tool use. And it takes say 14 seconds to execute. You just destroyed the developer experience. The agent will feel completely unresponsive. Speed is the absolute bottleneck here. OK, but if I have a heavy validation script, maybe my type checker is just inherently slow because of the code base size or I need to run a complex security linter. I still want that automated feedback loop. Yeah. But I clearly cannot bind it to a high frequency event like a file. Right. So you move the execution trigger to a lower frequency event. Instead of binding the script to file modifications, you can figure the JSON match to only run the pre tool use hook when the agent attempts to execute a specific bash command like git commit. Exactly. The heavy validation only runs when a logical chunk of work is actually being finalized. That preserves the interloop speed while still providing automated feedback before the code hits version control. That latency issue also applies to the session initialization, doesn't it? Like if you have three different set of scripts, one fetching get data, one pinging the ticket tracker and one checking the status of a local dev server running them sequentially is going to compound the startup delay. Yeah, you really want to avoid that. The goal should be flattening those operations into a single fast bash script that executes in parallel or exits early, keeping the total runtime under a second. Consolidating the execution path is essential for maintaining momentum. And, you know, we touched on blocking earlier, but it really bears repeating as a primary anti pattern. Missing dependencies cannot crash the session. Right. A simple if command dash VGH check before attempting to execute the GitHub CLI prevents catastrophic workflow failures. Yes. But actually the most dangerous anti pattern is security related. Remember, we are dealing with Jason files that get committed to the shared repository. Oh, which means we run the immediate risk of committing secrets. Exactly. If a team configures a session start hook to pull tickets from Jira or linear, the instinct is just to drop the API key straight into the bash command array inside dot cloud slash settings dot JSON. And hard coding credentials into the hook configuration is a massive security violation. The implementation must rely entirely on intent based referencing. How does that look in practice? Well, you can figure the shell command in the JSON to look for an environment variable, for example, dollar sign linear underscore API underscore key. Got it. You then ensure that variable is sourced from a local dot envy file that is strictly added to your repositories dot getting nor the hook executes. The agent gets the necessary context and your repository remains completely secure. That is so important. So the source material concludes by laying out the raw mathematical return on investment for configuring all of this and is pretty hard to argue with the final numbers. Yeah, the ROI is undeniable. An optimized session start hook printing, the current branch, the date and your local context file takes roughly 300 milliseconds to execute and generates about 200 tokens of output. Wow. That microscopic compute cost immediately replaces two to three minutes of manual prompting and state reconciliation. So if you're average five coding sessions a day, you are reclaiming up to 15 minutes of pure focus time. You write a basic bash script once. It takes you maybe 10 minutes and the system pays for itself before lunch. Exactly. And catching your own type errors in the background via postal use means zero failed builds during PR review. The compound interest on that saved iteration time scales massively across an organization. It fundamentally shifts the interaction model. The interface transforms from this reactive query engine that you have to constantly micromanage and correct into a contextually aware system that just anticipates the repository state. Right. It goes from being a tool to being a collaborator. So we always close with a concrete. Try this tomorrow takeaway for you listening before you pull your first ticket in the morning, spend 10 minutes writing those 10 lines of a session start hook in dot cloud slash settings. Dot Jason, I'll put your git status, cat a quick sticky note, mark down file and echo the date. It is definitively the highest leverage code you will write all week. I guarantee the contrast between an unconfigured session and a contextually anchored session is stark. Once you establish that automated bucks line, working without it feels completely broken. Yeah, I bet. But you know, thinking about that human written markdown file, the digital sticky note we leave to explain what we're working on, it prompts an interesting timeline question. Oh, if we are currently spending our afternoons writing lightweight context files to orient our AI agents for the next morning, how long until the agents are the ones writing the context files at the end of the day, leaving a perfectly prepared sticky note for our next human coding session. Yeah, that is an interesting thought. Something to think about next time you push a commit. Thanks for joining us on this deep dive. We'll catch you on the next one.

You open a new session. The agent asks: “What are we working on today?” Or worse — it doesn’t ask. It assumes. It looks at the last few files, picks up a thread from three days ago that you already shipped, and starts doing the wrong thing with full confidence.

Both outcomes cost you the same thing: the first two to three minutes of every session, narrating context the agent should already have. Multiply that across five sessions a day. Multiply that across the team. That’s real time, and it’s entirely recoverable with a hooks configuration.


What hooks are

Hooks are shell commands that run on lifecycle events in the agent session. Claude Code exposes four:

  • SessionStart — runs once when you open a new session
  • PreToolUse — runs before the agent calls a tool (before writing a file, before running a command)
  • PostToolUse — runs after the agent calls a tool
  • Stop — runs when the agent finishes its response

The hook’s stdout is injected into the agent’s context as a system reminder. The agent reads it. There’s no parsing, no special format — plain text works.


Where they live

Hooks live in settings.json. For project-scoped hooks, that’s .claude/settings.json (committed, team-shared). For global hooks that apply across all projects, ~/.claude/settings.json.

The JSON structure:

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "cat .claude/context.md 2>/dev/null || true"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "pnpm tsc --noEmit 2>&1 | tail -20"
          }
        ]
      }
    ]
  }
}

The matcher field on PreToolUse and PostToolUse filters by tool name: "Write", "Bash", "Read", etc. Without a matcher, the hook runs on every tool call — usually too noisy.


Session-start hooks worth writing

The single most useful hook. Zero configuration, pure orientation.

{
  "type": "command",
  "command": "echo '=== Session context ===' && git branch --show-current && echo '--- Last 3 commits ---' && git log --oneline -3 && echo '--- Uncommitted changes ---' && git status --short"
}

The agent now knows which branch you’re on, what the last three commits say about the work in flight, and whether there are uncommitted changes waiting. This alone prevents the “started working on the wrong thing” failure mode.

Obvious in retrospect. The agent’s training has a cutoff date. If you don’t tell it the date, it guesses — and for anything time-sensitive (changelogs, deprecation dates, seasonal work), it guesses wrong.

{
  "type": "command",
  "command": "echo \"Today: $(date '+%Y-%m-%d %A')\""
}

Four tokens of output. Potentially large impact on anything date-aware.

Cat a context file

Keep a .claude/context.md in the repo — a human-written note about what’s currently being worked on. Not automated. Updated by you when you switch focus.

{
  "type": "command",
  "command": "cat .claude/context.md 2>/dev/null || echo 'No context file found — create .claude/context.md to orient the agent.'"
}

The context file is the lightweight answer to “what’s in flight.” It doesn’t replace CLAUDE.md — that’s for permanent conventions. It’s the “right now” signal. Mine typically looks like:

# Current focus — updated 2026-05-17
 
Working on the newsletter signup form (src/components/marketing/NewsletterForm.astro).
Endpoint configured in .env as PUBLIC_NEWSLETTER_ENDPOINT.
DO NOT touch the layout files — unrelated refactor in progress on main.
 
Next: add the success/error state UI, then write the integration test.

When the session starts, the agent reads this and begins with accurate context instead of inference.

If your team uses Beads, Linear, or any CLI-accessible task tracker, a hook that prints the current open tasks gives the agent a ranked list of what matters right now.

{
  "type": "command",
  "command": "bd ready 2>/dev/null | head -10 || true"
}

The || true ensures the hook doesn’t block the session if the tool isn’t installed. Always include a fallback on commands that might not exist.


PreToolUse hooks worth writing

Validate a filename before a Write

If your project has strict naming conventions — components in PascalCase.tsx, hooks in useThing.ts — a PreToolUse hook can catch violations before the file is written.

{
  "matcher": "Write",
  "hooks": [
    {
      "type": "command",
      "command": "echo 'Writing file — ensure PascalCase for components, camelCase for utils, kebab-case for pages.'"
    }
  ]
}

This is a soft reminder, not a hard gate. For hard enforcement, use a pre-commit script. The hook is for real-time awareness during the session.


PostToolUse hooks worth writing

Run TypeScript check after file writes

Every time the agent writes a .ts or .tsx file, run a type check. The output goes back to the agent. If there are errors, the agent fixes them before you see the result.

{
  "matcher": "Write",
  "hooks": [
    {
      "type": "command",
      "command": "pnpm tsc --noEmit 2>&1 | grep -E 'error TS|warning TS' | head -15 || echo 'Type check clean'"
    }
  ]
}

This closes the loop that most agentic workflows leave open: write code, move on, discover type errors at build time. With this hook, type errors are part of the agent’s immediate feedback loop.

Auto-format after edits

{
  "matcher": "Write",
  "hooks": [
    {
      "type": "command",
      "command": "pnpm prettier --write $(git diff --name-only HEAD 2>/dev/null | head -5 | tr '\\n' ' ') 2>/dev/null || true"
    }
  ]
}

Keeps the diff clean without requiring the agent to think about formatting.


Stop hooks worth writing

Log session output to a file

{
  "hooks": [
    {
      "type": "command",
      "command": "echo \"$(date '+%Y-%m-%d %H:%M') — Session ended on $(git branch --show-current)\" >> .claude/session-log.txt"
    }
  ]
}

Over time, this becomes a lightweight audit trail. Useful when you can’t remember which session made a particular decision.


What NOT to put in hooks

Anything slow. Hooks run synchronously. A hook that takes five seconds delays every session start, every file write. If your type check takes fifteen seconds, run it as a PreToolUse on Bash(git commit) instead.

Anything that can fail and block. Always end commands with || true or 2>/dev/null || true when the tool might not be installed. A failing hook that isn’t silenced will block the session.

Secrets. Hook commands are stored in .claude/settings.json, which is committed to git. Don’t put API keys, tokens, or credentials in hook commands. Use environment variables sourced from .env (gitignored), and reference them as $MY_API_KEY in the command.

More than one heavy command in a session-start hook. Combine multiple pieces of context into a single script. Three separate commands each adding latency compound. One well-structured script that outputs everything in two seconds is the target.


The cost calculation

A session-start hook that prints branch, date, recent commits, and the context file takes about 0.3 seconds to run and produces roughly 200 tokens of output. That output replaces two to three minutes of orientation per session — the agent asking clarifying questions, guessing the wrong branch, repeating context you already shared.

At five sessions a day, that’s ten to fifteen minutes recovered daily. Per developer. The hook took ten minutes to write. It pays for itself in the first day.

The PostToolUse type-check hook is harder to quantify but the direction is clear: catching type errors immediately during agentic writes means zero “I didn’t notice there were type errors” moments at PR review. The saved back-and-forth compounds.

The highest-leverage ten lines you’ll write this week are the SessionStart hook that tells the agent what branch it’s on, what’s in flight, and what today’s date is. Everything else is optimisation on top.


Coming next

Project Settings, Permissions, and Team Sharing — the three files that control what Claude Code is allowed to do, what your team shares, and how to stop the permission prompts that break every session flow.

About the author

Prakash Poudel Sharma

Engineering Manager · Product Owner · Varicon

Engineering Manager at Varicon, leading the Onboarding squad as Product Owner. Eleven years of building software — first as a programmer, then as a founder, now sharpening the product craft from the inside of a focused team.

Configure Your AI Coding Environment

5 parts in this series.

A five-part guide to setting up your .claude folder, CLAUDE.md, hooks, permissions, and cross-tool configuration — the prerequisite for every other agentic workflow.

  1. 01The Two Configuration Layers Every AI Developer Needs
  2. 02Writing CLAUDE.md That Agents Actually Followprevious
  3. 03Hooks That Pay for Themselves← you are here
  4. 04Project Settings, Permissions, and Team Sharingup next
  5. 05Not on Claude? The Cross-Tool Configuration Guide
Join the conversation0 comments

What did you take away?

Thoughts, pushback, or a story of your own? Drop a reply below — I read every one.

Comments are powered by Disqus. By posting you agree to theirterms.

0:000:00