CLAUDE LABJP
FORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtaskLIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its ownMCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSPLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callbackSONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as OctoberFORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtaskLIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its ownMCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSPLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callbackSONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as October
Articles/Claude Code
Claude Code/2026-05-22Intermediate

Six Weeks Running a Claude Code Hooks + SwiftLint Quality Gate on My Indie iOS Apps

A six-week field report on wiring SwiftLint into Claude Code's PostToolUse hook to keep my indie iOS codebases consistent. Motivation, implementation, three adjustments that mattered, and the rough edges I hit along the way.

claude-code129hooks14swiftlintios14indie-dev14

I shipped my first wallpaper app in 2014 and have been building solo ever since. I am Masaki Hirokawa, an artist and creator who keeps a small indie app business (over fifty million cumulative downloads across several apps) running alongside my art practice.

Claude Code clearly speeds up code generation. But when one person maintains several apps, the generated code drifts a little from the rest of the codebase in subtle ways: indentation width, naming, import order, when to use guard versus if let. You notice it in a single file. Across ten or twenty files, it slowly erodes the feeling that "I wrote this." That bothered me.

Six weeks ago I wired SwiftLint into Claude Code's PostToolUse hook so that style checks run automatically the instant a file is edited. It is not a perfect system. But for keeping the codebase recognizably mine, it has been more effective than I expected. This is a record of those six weeks.

Why I bothered: keeping my codebase recognizably mine

Both of my grandfathers were miyadaiku, traditional shrine carpenters. On a miyadaiku site, every new piece of timber is checked for "alignment" with the existing pillars and beams. How nails are driven, the angle of a chamfer, the direction of the grain. Even details that no one will see in the finished structure have a standard. I want to keep the same feeling toward my code.

The Swift Claude Code outputs is fine as a baseline. But details like whether to omit return inside SwiftUI closures, the order of @State properties, or comment conventions vary in ways prompts cannot fully pin down. I used to fix this in manual review, but across several apps and dozens of edited files the review load piled up.

The plan was simple: run SwiftLint the instant Claude Code finishes editing a file, and let Claude itself fix the violations. A linter that only runs in CI is too slow. It has to run at the exact moment of the edit.

The minimal PostToolUse hook

Claude Code hooks let you run shell commands around tool calls. The goal here is to invoke SwiftLint right after Edit, Write, or MultiEdit.

Add a PostToolUse hook to ~/.claude/settings.json:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "bash $HOME/.claude/scripts/swiftlint-after-edit.sh"
          }
        ]
      }
    ]
  }
}

The matcher narrows the scope so the linter does not fire on Read or Grep. I started without a matcher, watched my thinking flow slow down, and quickly limited it to edit-class tools.

The script itself is minimal:

#!/usr/bin/env bash
# ~/.claude/scripts/swiftlint-after-edit.sh
set -euo pipefail
 
INPUT="$(cat)"
FILE_PATH="$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')"
 
# Do nothing for non-Swift files
[[ "$FILE_PATH" == *.swift ]] || exit 0
 
# Walk up to find the nearest .swiftlint.yml
DIR="$(dirname "$FILE_PATH")"
while [[ "$DIR" != "/" ]]; do
  [[ -f "$DIR/.swiftlint.yml" ]] && break
  DIR="$(dirname "$DIR")"
done
 
# Skip projects with no .swiftlint.yml
[[ -f "$DIR/.swiftlint.yml" ]] || exit 0
 
cd "$DIR"
OUTPUT="$(swiftlint --quiet --path "$FILE_PATH" 2>&1 || true)"
 
if [[ -n "$OUTPUT" ]]; then
  echo "SwiftLint findings for ${FILE_PATH}:"
  echo "$OUTPUT"
fi

The jq line that extracts tool_input.file_path is quietly important. Hooks receive JSON on stdin; misread it and you silently get nothing forever. I lost the first few days to exactly that.

Three adjustments after six weeks of use

1. One .swiftlint.yml per app worked better than a shared one

My laptop has wallpaper apps and calm-themed apps side by side, and they style their UI differently. I started with a single shared .swiftlint.yml and discovered that what was correct in one codebase counted as a violation in the other.

I moved to a .swiftlint.yml at the root of each repository and let the script find the nearest one. Allowing per-project dialects was a better fit for real-world maintenance.

2. Stop running --fix from inside the hook

At first I ran swiftlint --fix --quiet --path "$FILE_PATH" to auto-correct in the hook. It was convenient, but rewriting a file while Claude Code is still editing it confuses Claude's diff awareness. One time the auto-fix collided with Claude's next edit and the same fix was applied twice.

Since then the hook only reports findings. Claude reads them on the next turn and decides whether and how to fix.

3. Treat the noisy first week as noise

In week one, every edit to legacy files surfaced a wall of pre-existing violations. Claude got confused and started reaching for files it should not have touched.

I narrowed .swiftlint.yml by adding excluded: paths and restricting only_rules::

only_rules:
  - trailing_whitespace
  - colon
  - comma
  - vertical_whitespace
  - opening_brace
  - control_statement
  - identifier_name
  - line_length
 
excluded:
  - Pods
  - Carthage
  - Generated
  - Legacy   # ten-year-old code lives here

Whether to opt-in everything or start small is a matter of taste. I found starting small and adding trusted rules over time less stressful.

Four things that changed in six weeks

After those three adjustments the system felt stable. What I notice in daily use:

First, review time roughly halved. The style violations SwiftLint can see now mostly vanish inside the same Claude turn, freeing my eyes for design judgment and test coverage.

Second, the style of Claude Code's output converges within a session. When the hook returns a violation, Claude fixes it on the next turn, and that learning sticks for the rest of the session. I no longer have to explain in prose that "we omit return in this repo" — the linter teaches it backwards.

Third, the psychological cost of touching legacy files dropped. Even in code that mixes Objective-C from a decade ago, the hook tells Claude mechanically which lines to fix, so I can comfortably ask for "the smallest diff that resolves the SwiftLint findings only."

Fourth, slightly surprisingly, commit messages got more specific. Because violations are explicit, Claude writes messages like "Fix four trailing_whitespace and two identifier_name issues."

Rough edges I hit

To be honest, here are the rough edges I have lived with.

swiftlint's startup overhead is noticeable when fired per file. On an M1 Mac each invocation is a few hundred milliseconds, but right after a MultiEdit touches ten files at once the tempo drops visibly. --quiet, narrower only_rules:, and broader excluded: all help, but as long as the linter runs on every edit it will not be zero.

The other one is that long hook stdout slowly chews through Claude Code's context. I capped each file's findings to roughly ten lines with a trailing head -20. That was enough.

What I want to try next

The same shape generalizes to any linter or type checker. I am about to do the same thing in my Python scripts using ruff.

Whenever I think about what I want to leave for my children who live apart from me, I am reminded why it is worth writing the technical details down clearly. Even with a capable partner like Claude Code, whether the codebase still feels like mine depends on how I design the running surface around it. A thin mechanism like Hooks, run long enough, makes that difference visible.

If you are also a one-person shop bouncing between repositories, I hope some of this is useful. Thank you for reading.

Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Claude Code2026-06-02
Two Weeks of Splitting iOS Work Between Claude on Xcode and Claude Code
I ran Claude on Xcode, which lives in the Xcode sidebar, alongside Claude Code in the terminal across two weeks of real wallpaper-app work. Here is how I ended up dividing the tasks, and the simple rule I use to decide which one to open.
Claude Code2026-05-23
Notes from May 2026: Running a Parallel StoreKit 2 Migration Across Four iOS Wallpaper Apps with Claude Code
Operational notes from running a four-app iOS StoreKit 2 migration in parallel with Claude Code on a single Mac mini M5, captured during May 2026.
Claude Code2026-06-14
Running Claude Code Hooks as a Quality Gate Without Breaking Your Pipeline
An implementation note on running Claude Code Hooks as a safety valve for automation: when to block with exit code 2 versus JSON output, how to keep formatters from looping or over-blocking, and how to log every hook firing so misfires are traceable.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →