[{"content":" If you have less than 1GB of memory, decide what the model has to do before you decide which model. For fixed-format jobs like tool calling and JSON extraction, a 14MB Needle 2 or a 350M LFM2.5 is enough. For open-ended chat, 1B is the floor.\nI get asked a lot about running a language model on a Raspberry Pi, a low-spec VPS, or a laptop from 2018. The honest answer as of September 2026 is that there is no single \u0026ldquo;best tiny model\u0026rdquo;. For tasks with a fixed output shape (call this tool, extract these fields, classify this request), a 45M to 350M specialist model is often the better choice. For summarization or conversation, 1B is the minimum that produces more than a few coherent lines.\nThis post merges three earlier write-ups I did on on-device models, Needle 2, and local LLM hardware into one list. Every memory and speed number comes from the vendor\u0026rsquo;s model card or docs. Where I did not measure something myself, I say so and link the source.\nWhy small models are interesting again Through 2024 the local LLM conversation was about how to fit a 7B or 13B model on a home GPU. In 2026 there is a second track. Vendors are now designing sub-1B models for on-device use from the start: Google\u0026rsquo;s Gemma 3 270M and FunctionGemma, Liquid AI\u0026rsquo;s LFM2.5-350M, Cactus Compute\u0026rsquo;s Needle 2.\nTwo things changed. The first is training volume. LFM2.5-350M has 350 million parameters and was trained on 28 trillion tokens, roughly 80,000 tokens per parameter. That is far longer than earlier small models were trained, and instruction following improved noticeably at the same size.\nThe second is quantization. Storing weights in 4 bits instead of 16 cuts file size to about a quarter. Llama 3.2 1B at Q4_K_M needs around 1.5GB of RAM, and 30 to 50 tokens per second on Android phones has been reported.\nThe shortlist, September 2026 Pick on two axes: your memory ceiling, and the kind of task.\nSorted by memory, smallest first. Memory figures are vendor-published and vary with device and context length.\nModel Params Size / memory (official) Good at Not for Needle 2 (Cactus) 45M 14MB binary, ~28MB per session Tool calling, device control, JSON extraction Chat, summarization, inputs over 256 tokens FunctionGemma 270M (Google) 270M 288MB (S25 Ultra) Function calling after fine-tuning on your commands Chat, use without fine-tuning LFM2.5-350M (Liquid AI) 350M Under 1GB, 32K context Data extraction, structured output, tool use Knowledge questions, coding Qwen3 0.6B (Alibaba) 0.6B ~0.5GB at Q4, 32K context Multilingual short chat, classification, thinking mode Long reasoning, factual accuracy Llama 3.2 1B (Meta) 1.2B ~1.5GB RAM at Q4_K_M Routing, classification, autocomplete, short chat Complex instructions, math Gemma 4 E2B (Google) ~2B effective ~1.3GB download at Q4_K_M, 607MB peak on iOS Audio and image input, function calling, summarization Phones under 6GB RAM, microcontrollers Needle 2: 14MB and only does tool calling Needle 2 is a 45M parameter model released in summer 2026. The weights are compiled into the executable, so there is no separate model file, and one session runs in about 28MB. The official page claims over 500 tokens per second on a Raspberry Pi 5 and says it runs on an ESP32-S3 microcontroller.\nThe catch is a 256-token sliding window. Chat and summarization are simply out. On the official benchmarks it scores 63.7% on Mobile Actions and 42.6% on BFCL v4, behind LFM2.5 230M (69.1% and 60.8%) and about level with FunctionGemma 270M (64.0% and 46.1%). For a model 5 to 70 times smaller that is remarkable, but \u0026ldquo;remarkable for its size\u0026rdquo; is not the same as accurate. If your memory ceiling is 30MB, this is the only option. Otherwise it is not the default.\nFunctionGemma 270M: assumes you will fine-tune Google retrained Gemma 3 270M for function calling and shipped it in December 2025. The model card lists 125.9 tokens per second decode on a Samsung S25 Ultra and a 288MB footprint. Context is 32K, so it takes much longer inputs than Needle 2.\nDo not expect it to work out of the box. In Google\u0026rsquo;s own Mobile Actions example the base model scores 58% and the fine-tuned one 85%. You need to teach it your app\u0026rsquo;s command set once. That is why Google ships a fine-tuning tutorial and an AI Edge Gallery demo alongside it.\nLFM2.5-350M: the most balanced choice under 1GB Liquid AI released this 350M model in April 2026. On IFEval (instruction following) the model card reports 76.96, ahead of Gemma 3 1B (63.49) and Qwen3.5-0.8B (59.94). On MMLU-Pro (knowledge) it scores 20.01, far behind Qwen3.5-0.8B at 37.42. Liquid\u0026rsquo;s own guidance says not to use it for knowledge-heavy tasks or programming.\nIt runs in under 1GB, ships official GGUF files, and works in llama.cpp directly. The profile is clear: follows instructions well, uses tools well, knows almost nothing. That fits form filling, receipt extraction, and command classification, where the answer is already in the input. If I had to name one general-purpose model under 1GB, this is it. One caveat: the license is Liquid\u0026rsquo;s own LFM license, not Apache, so read it before shipping commercially.\nQwen3 0.6B: when you need multilingual or a thinking mode The smallest model in the Qwen3 series from May 2025. Of the 0.6B parameters, 0.44B are non-embedding. It has 32K context, an Apache 2.0 license, over 100 languages, and a /think switch that toggles reasoning mode.\nIf you need non-English input, and for me that means Korean, this is the first model to test. Most of the others in the table are trained mainly on English. Do not expect factual accuracy from it. Use it to summarize or classify what you give it.\nLlama 3.2 1B: the floor for \u0026ldquo;real sentences\u0026rdquo; Most local LLM comparisons agree on one point: below 1B, output stops holding together after a few lines. Llama 3.2 1B sits on that line. At Q4 it is about a 1GB file, 1.3 to 1.5GB of RAM, and 60 to 90 tokens per second on CPU has been reported.\nIt is a September 2024 model and the benchmarks show it. Liquid AI\u0026rsquo;s published numbers put LFM2.5-1.2B at 86.23 on IFEval versus 52.37 for Llama 3.2 1B. It still gets used because it has the most examples and quantized files in llama.cpp, Ollama, MLC LLM, and the mobile SDKs. If you are setting up a pipeline for the first time, validate it with this model because everything supports it, then swap.\nGemma 4 E2B: audio and images on a 6GB phone The edge model in the Gemma 4 family from April 2026. \u0026ldquo;E2B\u0026rdquo; means about 2B effective parameters: the model is larger, but only part of it is active, so it runs in 2B-class memory. Google\u0026rsquo;s AI Edge docs list 607MB peak memory on iOS and 160 tokens per second decode on a macOS GPU. Audio and image input and function calling are built in, under Apache 2.0.\nIt is the largest model on the list and does the most. If your target is a phone with 6GB or more or a laptop with 8GB or more, start here and save yourself the time. On a Raspberry Pi 4 or a 4GB laptop it is not realistic.\nWhat you get and what you give up The upsides are the reasons people go on-device in the first place. Inference costs nothing per call and the input never leaves the device, which in healthcare, finance, or anywhere without a network is the whole business case. They are fast: models under 350M produce hundreds of tokens per second on CPU alone. LFM2.5-350M reports 313 tokens per second on an AMD CPU, Needle 2 over 500 on a Pi 5. And fine-tuning fits on a laptop. Needle 2 runs LoRA training from a one-line CLI, FunctionGemma has a tutorial you can follow verbatim. \u0026ldquo;Retrain on my data\u0026rdquo; becomes something you actually do.\nThe downsides are just as concrete. They know nothing. Sub-350M models score in the 20s on MMLU-Pro. Ask one what the minimum wage is this year and you will get a confident wrong number. Only give them tasks where the answer is in the input. Context is short, or if it is long, memory grows with it. Needle 2 caps at 256 tokens. Qwen3 0.6B supports 32K, but the KV cache (the stored computation for tokens already read) grows in proportion, so on low-spec hardware you limit context to 1K or 2K. And they run hot. Inference pins the processor near its ceiling. On an always-on device, measure energy per call before tokens per second.\nHands-on: LFM2.5-350M on llama.cpp Of the six, LFM2.5-350M is the one with official GGUF files, so it is the example. Needle 2 installs separately with pip install cactus-needle.\nBuild llama.cpp. On Apple Silicon, Metal acceleration is enabled automatically.\ngit clone https://github.com/ggml-org/llama.cpp cd llama.cpp cmake -B build \u0026amp;\u0026amp; cmake --build build --config Release -j Pull the GGUF from Hugging Face and start the server. -c 2048 caps context at 2K to save memory. -ngl 0 means CPU only.\n./build/bin/llama-server \\ -hf LiquidAI/LFM2.5-350M-GGUF \\ -c 2048 -ngl 0 --port 8080 That exposes an OpenAI-compatible API, so existing client code works unchanged:\ncurl http://localhost:8080/v1/chat/completions \\ -H \u0026#34;Content-Type: application/json\u0026#34; \\ -d \u0026#39;{\u0026#34;messages\u0026#34;:[{\u0026#34;role\u0026#34;:\u0026#34;user\u0026#34;,\u0026#34;content\u0026#34;:\u0026#34;Extract the date and amount as JSON: paid 45,000 KRW on September 12\u0026#34;}]}\u0026#39; At this size the model card numbers put CPU throughput in the hundreds of tokens per second, so latency is not the problem. Format drift is. Small models will occasionally rename a field or wrap the JSON in prose. In production, pair it with llama.cpp\u0026rsquo;s schema enforcement (--grammar or response_format) rather than hoping.\nIf you have any GPU at all, -ngl 99 moves every layer onto it. A 350M model fits in 1GB of VRAM.\nPicks by situation Voice commands to device actions on a Pi 5 or an ESP32: Needle 2. Stays under 30MB and the fine-tuning loop lives in the same repo.\nDocument field extraction or request classification on a 4GB mini PC: LFM2.5-350M behind llama-server. Follows instructions and is fast on CPU.\nMultilingual input with about 1GB to spare: Qwen3 0.6B. The only tiny model on the list with broad multilingual training.\nAudio or image input in a mobile app targeting 6GB+ phones: Gemma 4 E2B via LiteRT-LM. Kotlin, Swift, and Flutter SDKs are official.\nAn \u0026ldquo;on-device first, cloud if unsure\u0026rdquo; router: put Needle 2\u0026rsquo;s confidence gating or LFM2.5\u0026rsquo;s fast response in front, and forward only low-confidence requests to an API model. That is where the cost savings actually show up.\nWho this is for Embedded and IoT developers building things that must work offline. Mobile developers who cannot send user data to a server. Backend developers who want to filter simple requests locally before paying for an API call.\nIf you want a general chatbot or code generation, every model here will disappoint you. Move up to at least 3B or 4B (Qwen3 4B, Gemma 4 E4B, Phi-4-mini) and plan for an 8GB GPU or 16GB of unified memory.\nBefore you ship Check the license first. Needle 2, Qwen3, and Gemma 4 are Apache 2.0. The Gemma 3 270M family is under the Gemma license and LFM2.5 under Liquid\u0026rsquo;s LFM license. Commercial terms differ.\nMeasure accuracy on your own data. The benchmark numbers above are English, and tool-calling accuracy depends heavily on how well your tools are described. Fifty to a hundred representative requests with a pass/fail count is the starting point.\nBudget context and memory together. Real memory is the model file plus the KV cache. Capping context at 2K keeps most devices in a predictable range.\nDecide on an update cadence. LFM2.5-350M, Gemma 4, and Needle 2 all shipped in the first half of 2026. Half this table will probably be different in six months. Hide the model behind an OpenAI-compatible endpoint so swapping is a config change.\nFAQ Does this work on a Raspberry Pi 4 with 4GB? Yes. Needle 2, LFM2.5-350M, and Qwen3 0.6B run comfortably. Llama 3.2 1B runs at Q4 but drops to a few tokens per second. Skip Gemma 4 E2B.\nNo GPU at all? For models under 350M, CPU is the primary target anyway. Needle 2 was built for CPUs and microcontrollers, and LFM2.5 markets its CPU speed. GPU acceleration starts to matter at 1B and above.\nWhich one handles non-English best? Among the tiny ones, Qwen3 0.6B has the widest multilingual training. The rest are English-centric and will drift on formatting in other languages. If language quality matters, go up to Qwen3 1.7B or Gemma 4 E2B.\nWrapping up The selection order is memory ceiling, then task, then language. Under 30MB, Needle 2. Under 1GB, LFM2.5-350M or Qwen3 0.6B. Around 1.5GB, Llama 3.2 1B. A 6GB phone or 8GB laptop, Gemma 4 E2B. That is the September 2026 answer.\nThe one piece of advice that applies to all of them: do not ask these models about the world. Give them tasks where the answer is in the input. Under that constraint a 350M model is genuinely useful. Spin up a llama-server this weekend and measure your success rate on your own data before you believe anyone\u0026rsquo;s table, including this one.\nSources Google AI Edge, Gemma 4 model docs: E2B and E4B sizes, per-platform peak memory, decode speed, LiteRT-LM SDKs Google, FunctionGemma model card: 270M specs, BFCL scores, S25 Ultra speed, accuracy before and after fine-tuning Hugging Face, google/gemma-3-270m: 270M base model specs and license Hugging Face, LiquidAI/LFM2.5-350M: 350M benchmarks, per-device speed, recommended uses, license Liquid AI blog, Introducing LFM2.5: 1.2B benchmarks and CPU/NPU memory numbers Hugging Face, Qwen/Qwen3-0.6B: parameter breakdown, context, thinking mode GitHub, cactus-compute/needle: Needle 2 specs, install, fine-tuning CLI Cactus Compute, Needle 2 page: benchmark table, per-device speed, supported platforms GitHub, ggml-org/llama.cpp: build and server commands Popular AI, Best CPU-only local LLMs in 2026: model size guide by RAM ","permalink":"https://cskwork.github.io/posts/tiny-on-device-llms-sub-1b-models-2026/","summary":"Under 1GB of RAM you pick by task, not by benchmark. A 14MB tool-calling model beats a 1B chat model if all you need is tool calls. Here is the shortlist, the memory numbers, and where each one falls over.","title":"Tiny on-device LLMs in 2026: which sub-1B models are worth running on a Raspberry Pi, an old laptop, or a CPU-only box"},{"content":"Earlier today Claude Code tried to run rm -rf on a directory I had asked it to replace. It got denied. It tried a shorter variant. Denied. It tried rmdir. Denied again. Then it gave up and used the file tools instead, which is exactly what I wanted, and I did nothing. A three-line deny list in ~/.claude/settings.json did the work, and it did it even though the session was running in auto mode.\nThat is the whole argument for spending an hour on your Claude Code setup. The model is good. The defaults are cautious. But the config is where you decide what \u0026ldquo;never\u0026rdquo; means, what runs without asking, and what the agent knows about your project before it reads a single file. This post walks through the files I run, in the order I would set them up on a new machine.\nWhere settings live, and which one wins Claude Code reads settings from several places and merges them. From highest to lowest priority:\nManaged policy (set by an org admin, you cannot override it) Command-line flags for the current session .claude/settings.local.json in the project (gitignored, personal) .claude/settings.json in the project (committed, shared with the team) ~/.claude/settings.json (your user defaults) The one rule that matters more than the ordering is this: a deny entry wins over an allow entry from any level. You can allow Bash(*) in a project and still have Bash(rm:*) blocked from your user file. That is the mechanism that stopped the rm -rf above.\nsettings.json, the parts worth copying Here is a trimmed version of my user-level file. The full one has about 150 allow entries because I let the built-in /fewer-permission-prompts command mine my history for read-only calls, and I keep whatever it suggests.\n{ \u0026#34;model\u0026#34;: \u0026#34;opus[1m]\u0026#34;, \u0026#34;effortLevel\u0026#34;: \u0026#34;low\u0026#34;, \u0026#34;includeCoAuthoredBy\u0026#34;: false, \u0026#34;env\u0026#34;: { \u0026#34;BASH_DEFAULT_TIMEOUT_MS\u0026#34;: \u0026#34;30000\u0026#34; }, \u0026#34;permissions\u0026#34;: { \u0026#34;allow\u0026#34;: [ \u0026#34;Read\u0026#34;, \u0026#34;Edit\u0026#34;, \u0026#34;Write\u0026#34;, \u0026#34;Glob\u0026#34;, \u0026#34;Grep\u0026#34;, \u0026#34;Bash(git status:*)\u0026#34;, \u0026#34;Bash(git diff:*)\u0026#34;, \u0026#34;Bash(git log:*)\u0026#34;, \u0026#34;Bash(gh pr view:*)\u0026#34;, \u0026#34;Bash(gh pr checks:*)\u0026#34;, \u0026#34;Bash(gh run view:*)\u0026#34;, \u0026#34;Bash(ls *)\u0026#34;, \u0026#34;Bash(cat:*)\u0026#34;, \u0026#34;Bash(rg:*)\u0026#34;, \u0026#34;Bash(jq:*)\u0026#34;, \u0026#34;WebSearch\u0026#34;, \u0026#34;WebFetch\u0026#34;, \u0026#34;mcp__context7__resolve-library-id\u0026#34;, \u0026#34;mcp__context7__query-docs\u0026#34; ], \u0026#34;deny\u0026#34;: [ \u0026#34;Bash(rm:*)\u0026#34;, \u0026#34;Bash(rm -rf:*)\u0026#34;, \u0026#34;Bash(rmdir:*)\u0026#34;, \u0026#34;Bash(sudo:*)\u0026#34;, \u0026#34;Bash(chown:*)\u0026#34;, \u0026#34;Bash(chmod 777:*)\u0026#34;, \u0026#34;Bash(kill:*)\u0026#34;, \u0026#34;Bash(killall:*)\u0026#34;, \u0026#34;Bash(pkill:*)\u0026#34;, \u0026#34;Bash(dd:*)\u0026#34;, \u0026#34;Bash(mkfs:*)\u0026#34; ] } } A few notes on what each line buys you.\n\u0026quot;model\u0026quot;: \u0026quot;opus[1m]\u0026quot; pins the 1M-context variant. If you mostly plan and then execute, \u0026quot;opusplan\u0026quot; is a cheaper alias: Opus while in plan mode, Sonnet once it starts editing. Subagents get their own model through an environment variable, and I set it to the cheapest one that can still read code:\nexport CLAUDE_CODE_SUBAGENT_MODEL=\u0026#34;haiku\u0026#34; effortLevel is the default reasoning effort. I keep it low and raise it per session with /model when a task deserves it. Running everything on high is how you burn a plan quota by Wednesday.\nThe allow list is mostly read-only git and gh commands. The pattern Bash(git log:*) matches git log followed by anything. Bash(ls *) and Bash(ls -*) are two separate entries because the matcher is literal about the space.\nThe deny list is short on purpose. I do not deny git push. I want to be asked, not blocked, because sometimes I do want the agent to push. rm is different. There is no session where I want an agent deleting things without a human looking at the target first. Deleting is the one action where \u0026ldquo;adjust and use another tool\u0026rdquo; is always the right fallback, so I make it the only option.\nincludeCoAuthoredBy: false keeps the Claude trailer out of commit messages. Your call, but decide it once here instead of editing commits later.\nPermission modes and auto mode The mode decides what happens when a call is neither allowed nor denied. default asks. acceptEdits auto-approves file edits and still asks for shell commands. plan is read-only. bypassPermissions asks nothing, and I only use it inside a throwaway container.\nAuto mode sits between acceptEdits and bypassPermissions. Instead of a static list it runs a classifier over each call, and you can feed that classifier a plain-English description of the environment in settings.json:\n{ \u0026#34;autoMode\u0026#34;: { \u0026#34;environment\u0026#34;: [ \u0026#34;Solo developer on macOS, trusted local machine.\u0026#34;, \u0026#34;Repositories are private unless the remote says otherwise.\u0026#34;, \u0026#34;No production database credentials on this machine.\u0026#34; ] } } The important detail from this morning: auto mode still respects deny. The classifier decides about the gray area. The deny list decides about the red area, and it never gets a vote.\nCLAUDE.md, what to put in it and what to move out CLAUDE.md is prose the model reads at the start of every session. There is a user-level one in ~/.claude/ and a project-level one in the repo root, and both load. Mine at the project level for a Hugo blog looks like this, cut down:\n# CLAUDE.md ## Setup pip install -r data_sources/requirements.txt ## Commands - `/research [topic]` writes a brief to research/ - `/write [topic]` drafts to drafts/ and runs the SEO agents - `/publish [slug]` publishes an approved post ## Content pipeline topics/ -\u0026gt; research/ -\u0026gt; drafts/ -\u0026gt; review-required/ -\u0026gt; site/content/posts/ ## Context files context/brand-voice.md, context/style-guide.md, context/seo-guidelines.md Build commands, directory layout, the names of your slash commands, the two or three rules people keep getting wrong. That is the job. Keep it short enough that you would reread it yourself. When the file grows past a couple of hundred lines the model starts treating parts of it as optional, which is what you would do too.\nTwo habits that help:\nWrite rules as positives. \u0026ldquo;Use named exports\u0026rdquo; lands better than \u0026ldquo;Do not use default exports\u0026rdquo;. A negative rule requires the model to hold the banned thing in mind, which is a great way to get it.\nMove anything you have repeated three times into a hook. CLAUDE.md is a request. A hook is a guarantee. If \u0026ldquo;run prettier after editing\u0026rdquo; is in your CLAUDE.md, it will happen most of the time. If it is a PostToolUse hook, it will happen every time, including at 3am in a headless run.\nThere is also a user-level ~/.claude/CLAUDE.md for things that are about you rather than the project. Mine says to ask before anything hard to reverse, to run one subagent at a time unless parallelism reduces total work, and to report what was verified rather than what was intended. Those are instructions I would otherwise type into every session.\nHooks, the only deterministic part Hooks are shell commands (or a prompt, or an HTTP call) that fire on lifecycle events. They receive a JSON payload on stdin and can block a tool call by exiting with code 2. This is the piece of the setup most people get wrong because a lot of blog posts show a config shape that does not match what the CLI reads. The real shape is: event name, then a list of matchers, each with its own list of hooks.\n{ \u0026#34;hooks\u0026#34;: { \u0026#34;PostToolUse\u0026#34;: [ { \u0026#34;matcher\u0026#34;: \u0026#34;Edit|Write\u0026#34;, \u0026#34;hooks\u0026#34;: [ { \u0026#34;type\u0026#34;: \u0026#34;command\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;jq -r \u0026#39;.tool_input.file_path\u0026#39; | xargs -I{} prettier --write {} 2\u0026gt;/dev/null || true\u0026#34; } ] } ] } } The matcher is a regex over tool names. An empty string or * matches everything. Each hook can carry a timeout in seconds and \u0026quot;async\u0026quot;: true if you do not want the agent to wait for it.\nEvents I have wired up in my own file, and what they are good for:\nEvent What I use it for SessionStart Load a summary of the last session from a memory tool. Anything the hook prints to stdout is added to the context UserPromptSubmit Update a tmux window status so I can see which pane is working PreToolUse Block dangerous shell commands; rewrite WebSearch queries to include the current year PostToolUse Format files after edits; log tool calls for later review PostToolUseFailure Desktop notification when something breaks PermissionRequest Notify me that a session is waiting on a prompt Stop Notify, and mark the window \u0026ldquo;done\u0026rdquo; SubagentStart / SubagentStop Record which subagents ran and what they returned PreCompact Snapshot state before context compaction SessionEnd Write a handoff note for the next session Three recipes from that table that I would put on any machine.\nBlock the commands you never want, at the hook level too. The deny list handles the exact patterns. This catches the creative ones.\n{ \u0026#34;hooks\u0026#34;: { \u0026#34;PreToolUse\u0026#34;: [ { \u0026#34;matcher\u0026#34;: \u0026#34;Bash\u0026#34;, \u0026#34;hooks\u0026#34;: [ { \u0026#34;type\u0026#34;: \u0026#34;command\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;jq -r \u0026#39;.tool_input.command\u0026#39; | grep -qE \u0026#39;rm -rf /|git push --force (origin )?(main|master)|DROP TABLE\u0026#39; \u0026amp;\u0026amp; exit 2 || exit 0\u0026#34; } ] } ] } } Tell the agent what day it is before it searches. Models have a training cutoff and will happily search for \u0026ldquo;best X 2025\u0026rdquo; in September 2026. A PreToolUse hook on WebSearch that appends the current year to the query fixed most of my stale-result problems.\nGet pinged when it stops. The macOS version is one line:\n{ \u0026#34;hooks\u0026#34;: { \u0026#34;Stop\u0026#34;: [ { \u0026#34;matcher\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;hooks\u0026#34;: [ { \u0026#34;type\u0026#34;: \u0026#34;command\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;osascript -e \u0026#39;display notification \\\u0026#34;Claude Code finished\\\u0026#34; with title \\\u0026#34;Claude Code\\\u0026#34;\u0026#39;\u0026#34; } ] } ] } } Keep hooks fast. Every synchronous hook adds latency to every matching tool call, and PostToolUse with matcher: \u0026quot;*\u0026quot; runs hundreds of times a session. Anything that talks to a network or a database gets \u0026quot;async\u0026quot;: true.\nSkills, commands, and agents Three directories under .claude/ turn markdown into behavior.\n.claude/commands/name.md becomes /name. The body is a prompt; $ARGUMENTS is whatever you typed after the command. My blog repo has /research, /write, /publish and a dozen more, and they are the reason a non-writer can run the pipeline.\n.claude/skills/name/SKILL.md is the newer, richer version: a directory with frontmatter, optional scripts, and a description the model uses to decide when to load it on its own. A minimal one:\n--- name: deploy-check description: Run the pre-deploy checklist (lint, tests, build, clean tree) and report pass or fail. allowed-tools: [\u0026#34;Bash\u0026#34;, \u0026#34;Read\u0026#34;, \u0026#34;Grep\u0026#34;] disable-model-invocation: true --- Run these in order and stop at the first failure: 1. `npm run lint` 2. `npm test` 3. `npm run build` 4. `git status --porcelain` must be empty If $ARGUMENTS names a branch, check it out first. Print PASS or FAIL with the failing step and its output. disable-model-invocation: true means only I can trigger it. Anything with side effects gets that flag. Skills without it are fair game for the model to invoke whenever the description matches, which is what you want for reference material and what you do not want for \u0026ldquo;publish to production\u0026rdquo;.\n.claude/agents/name.md defines a subagent: a system prompt plus a tool allowlist and an optional model. My blog has seo-optimizer, meta-creator, internal-linker, editor. They run after /write with their own context, which keeps the main conversation from filling up with the intermediate output.\nOn subagents generally: run one at a time unless running two actually reduces total work. Parallel agents look productive and mostly produce parallel mess that you then reconcile. The bottleneck is never generation. It is verification, and verification is serial because it is you.\nMCP servers MCP is how the agent gets tools that are not built in: GitHub, a database, a browser, a docs index. Adding one is a single command:\nclaude mcp add context7 --transport http --url https://mcp.context7.com/mcp claude mcp add-json postgres \u0026#39;{\u0026#34;type\u0026#34;:\u0026#34;stdio\u0026#34;,\u0026#34;command\u0026#34;:\u0026#34;npx\u0026#34;,\u0026#34;args\u0026#34;:[\u0026#34;@anthropic-ai/mcp-server-postgres\u0026#34;,\u0026#34;postgresql://localhost/mydb\u0026#34;]}\u0026#39; Servers live in one of three scopes. Local (~/.claude.json, just you, just this machine). Project (.mcp.json in the repo, committed, shared). User (~/.claude/settings.json, every project). Project scope is the right one for anything a teammate would need.\nEach MCP tool shows up in permissions as mcp__server__tool, so you can allow mcp__github__get_pull_request and still be asked about mcp__github__merge_pull_request. Do that. A database server gets a read-only connection string. No exceptions.\nI run about eight servers and use three daily: Context7 for library docs, a browser driver, and a memory server. The rest are project-specific. Tool schemas are deferred until the agent searches for them, so unused servers cost little context, but every server is code you are trusting with your shell. Read the source of community ones before adding them.\nHeadless mode and CI Everything above works without a terminal attached:\nclaude -p \u0026#34;Run the test suite and summarize failures as JSON\u0026#34; --output-format json That is the shape I use in scripts and cron. For pull requests there is an official action:\nname: Claude PR review on: pull_request: types: [opened, synchronize] jobs: review: runs-on: ubuntu-latest steps: - uses: anthropics/claude-code-action@v1 with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} prompt: | Review this PR for bugs and security issues. Be specific: file, line, why it is wrong, what to do instead. The same .claude/settings.json and CLAUDE.md in the repo apply in CI, which is the strongest reason to commit them.\nThe order I would do this in On a new machine, in this order, about an hour total:\n~/.claude/settings.json with a deny list for rm, sudo, kill, and a model choice. Ten minutes, and it is the ten minutes that matter. claude /init in the project to draft CLAUDE.md, then cut it to the commands, layout, and three rules. A PostToolUse formatter hook and a PreToolUse blocker hook. One skill for the thing you do every day, with disable-model-invocation: true if it deploys anything. One MCP server, project scope, read-only. Commit .claude/settings.json, .mcp.json, and CLAUDE.md. Gitignore settings.local.json. Everything else, the notification hooks, the memory servers, the agent teams, is nice to have. The deny list is not.\n","permalink":"https://cskwork.github.io/posts/claude-code-setup-settings-hooks-skills-mcp/","summary":"Most people run Claude Code on defaults. This is the config I actually use, with the parts that saved me from myself this week, and the parts I would rip out.","title":"A Claude Code setup that actually runs every day: settings.json, CLAUDE.md, hooks, skills, MCP"},{"content":"AI Insights Lab is written by a backend developer in Seoul who ships production software with AI coding agents every day and runs a small fleet of them for content, QA, and operations.\nThe English edition covers what actually works: Claude Code and agent configuration, agent workflows that survive contact with real repositories, what these tools cost in practice, and small language models on hardware you already own. Every setup described here runs on this machine before it is written up, and every number that is not measured here is linked to its source.\nThere is also a Korean edition with a broader scope, including AI news and market commentary.\nDisclosure Some links on this site may be affiliate links, which means the site may earn a commission if you buy something through them, at no extra cost to you. The site may also show ads. Neither changes what gets recommended: if a tool is in a post, it is because it was used.\nContact Open an issue or start a discussion on GitHub. Corrections are welcome and get credited.\n","permalink":"https://cskwork.github.io/about/","summary":"Who writes this and how.","title":"About"},{"content":"Last updated: September 12, 2026\nThis site is a static blog hosted on GitHub Pages. It has no accounts, no comment system, and no forms that store your data.\nWhat is collected Server logs. GitHub Pages, the hosting provider, may log requests (IP address, user agent, requested URL) to operate the service. See GitHub\u0026rsquo;s privacy statement.\nAnalytics. If Google Analytics is enabled, it sets cookies and collects anonymized usage data (pages viewed, approximate location, device type) so I can see which posts are read. You can opt out with the Google Analytics opt-out add-on or by blocking third-party scripts.\nAdvertising. If Google AdSense is enabled, Google and its partners use cookies to serve ads based on your prior visits to this and other sites. You can opt out of personalized advertising at Google Ads Settings and learn more at How Google uses information from sites that use its services.\nAffiliate links. Some outbound links carry an affiliate tag. Clicking one tells the merchant that you came from this site. No personal data is passed from this site to the merchant.\nWhat is not collected No email addresses, names, or account data. The search box runs entirely in your browser against a static index and sends nothing to a server.\nYour choices Blocking cookies or third-party scripts in your browser does not break anything on this site.\nContact Questions about this policy: open an issue on GitHub.\n","permalink":"https://cskwork.github.io/privacy/","summary":"What this site collects and why.","title":"Privacy policy"}]