{"id":903,"date":"2026-03-03T14:05:39","date_gmt":"2026-03-03T06:05:39","guid":{"rendered":"https:\/\/vinta.ws\/code\/?p=903"},"modified":"2026-08-11T14:07:03","modified_gmt":"2026-08-11T06:07:03","slug":"claude-code-useful-plugins-skills-and-mcps","status":"publish","type":"post","link":"https:\/\/vinta.ws\/code\/claude-code-useful-plugins-skills-and-mcps.html","title":{"rendered":"Claude Code: Things I Learned After Using It Every Day"},"content":{"rendered":"<h1>Claude Code: Things I Learned After Using It Every Day<\/h1>\n<h1>blog-post #cli-tool #dev-env<\/h1>\n<p>I've used Claude Code daily since it came out. Here are the best practices, tools, and configuration patterns I've picked up. Most of this applies to other coding agents (Codex) too.<\/p>\n<blockquote>\n<p>TL;DR<br \/>\nMy configs, plugins, and skills for Claude Code:<br \/>\n<a href=\"https:\/\/github.com\/vinta\/hal-9000\">https:\/\/github.com\/vinta\/hal-9000<\/a><\/p>\n<\/blockquote>\n<h2>CLAUDE.md<\/h2>\n<h3>The Global CLAUDE.md<\/h3>\n<p>Your <code>~\/.claude\/CLAUDE.md<\/code> should only contain:<\/p>\n<ul>\n<li>Your preferences and nudges to correct agent behaviors<\/li>\n<li>You probably don't need to tell it YAGNI or KISS as bare principles. They're already built in.<\/li>\n<\/ul>\n<p>Pro tip 1: before adding something to <code>CLAUDE.md<\/code>, ask it, <strong>&quot;Is this already covered in your system prompt?&quot;<\/strong><br \/>\nPro tip 2: try my <a href=\"https:\/\/github.com\/vinta\/hal-9000\/blob\/main\/skills\/refactor-claude-md\/SKILL.md\">refactor-claude-md<\/a> skill!<\/p>\n<p>Here are some parts of my <code>CLAUDE.md<\/code> I found useful:<\/p>\n<pre class=\"line-numbers\"><code class=\"language-markdown\">### Use AskUserQuestion\n\nWhen you need input and the answer is a selection rather than a sentence (multiple-choice, yes\/no confirmations that gate next steps, picking from a list, choosing between approaches), ask with the <code>AskUserQuestion<\/code> tool instead of plain text, so the user clicks an option instead of typing.\n\nWhen presenting approaches, put the summary in each option's label and the pros\/cons in its description. Plain text is fine when the answer is open-ended. This changes the format of questions, not whether to ask: never use it to ask permission for work you already have enough information to do.\n\n### Prefer online sources\n\nTraining data goes stale: library\/framework\/SDK APIs, config keys, CLI flags, cloud services, platform features, syntax, and versions change, and guessing has repeatedly cost debugging round-trips.\n\nInvoke the <code>find-docs<\/code> skill BEFORE writing code or config that touches any of those, and BEFORE answering questions about them. Being about to write such code is trigger enough, even when no question was asked. Confidence is not an exemption, and neither is the library being well known. Answering from training data, or fetching a remembered docs URL instead of invoking the skill, does not satisfy this rule.\n\nIf the user provides URLs, <code>WebFetch<\/code> each one as a primary source before searching further. Never skip user-provided URLs. For topics <code>find-docs<\/code> covers poorly, <code>WebFetch<\/code> the official docs instead of falling back to training data.\n\n### Auto-commit\n\nWhen you have completed the user's requested change, use the <code>commit<\/code> skill to commit, always passing why the changes were made (e.g. <code>\/commit add login endpoint for mobile app auth<\/code>); when no reason was stated, pass the request that prompted the changes instead \u2014 never an invented why.\n\nDon't batch unrelated changes into one commit.<\/code><\/pre>\n<p>Also see:<\/p>\n<ul>\n<li><a href=\"https:\/\/raw.githubusercontent.com\/vinta\/hal-9000\/refs\/heads\/main\/dotfiles\/.claude\/CLAUDE.md\">GitHub: vinta\/hal-9000 - The user-level CLAUDE.md<\/a><\/li>\n<li><a href=\"https:\/\/www.aihero.dev\/a-complete-guide-to-agents-md\">A Complete Guide To AGENTS.md<\/a><\/li>\n<\/ul>\n<h3>The Project CLAUDE.md<\/h3>\n<p>For project-specific instructions, put them in the project-level <code>CLAUDE.md<\/code>.<\/p>\n<p>The highest-signal content in your project <code>CLAUDE.md<\/code> (or any skill) is the <strong>Gotchas<\/strong> section. Build these from the failure points Claude Code actually runs into.<\/p>\n<p>Also see:<\/p>\n<ul>\n<li><a href=\"https:\/\/x.com\/trq212\/status\/2033949937936085378\">Twitter: @trq212 - Lessons from Building Claude Code: How We Use Skills<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/anthropics\/claude-plugins-official\/blob\/main\/plugins\/claude-md-management\/skills\/claude-md-improver\/references\/templates.md\">GitHub: anthropics\/claude-plugins-official - CLAUDE.md templates<\/a><\/li>\n<\/ul>\n<h2>Per File Type Rules<\/h2>\n<p>For language-specific or per-file <a href=\"https:\/\/code.claude.com\/docs\/en\/memory#organize-rules-with-claude\/rules\/\">rules<\/a>, put them in <code>~\/.claude\/rules\/<\/code>, so Claude Code only loads them when editing those file types.<\/p>\n<p>For instance, <code>~\/.claude\/rules\/typescript-javascript.md<\/code>:<\/p>\n<pre class=\"line-numbers\"><code class=\"language-markdown\">---\npaths:\n  - \"**\/*.{ts,tsx}\"\n  - \"**\/*.{js,jsx}\"\n  - \"**\/package.json\"\n---\n\n# TypeScript \/ JavaScript\n\n- Pin exact dependency versions in <code>package.json<\/code> \u2014 no <code>^<\/code> or <code>~<\/code> prefixes\n- Use <code>node:<\/code> prefix for Node.js built-in modules (e.g., <code>node:fs<\/code>, <code>node:path<\/code>)\n- Write proper types\/interfaces instead of <code>any<\/code> or casts like <code>as any<\/code> \/ <code>as unknown<\/code>\n  - When a value is genuinely untypable, use <code>unknown<\/code> and narrow it explicitly. <code>any<\/code> is the last resort when no typed alternative exists\n- Prefer <code>interface<\/code> over <code>type<\/code> for object shapes (extendable, better error messages)\n- Avoid enums. Use union types (<code>type Status = &#039;active&#039; | &#039;inactive&#039;<\/code>) or <code>as const<\/code> objects\n- Mark properties and parameters <code>readonly<\/code> when they should not be mutated\n- Do not add explicit return types. Let TypeScript infer them\n- Use the <code>typescript<\/code> LSP tool for type-aware code navigation when grep's text matching would be ambiguous<\/code><\/pre>\n<p>The full rules I have:<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/vinta\/hal-9000\/tree\/main\/dotfiles\/.claude\/rules\">GitHub: vinta\/hal-9000 - .claude\/rules\/<\/a><\/li>\n<\/ul>\n<h2>Output Styles<\/h2>\n<p>Claude Code provides a built-in method to modify the system prompt to change how Claude responds: <a href=\"https:\/\/code.claude.com\/docs\/en\/output-styles\">Output styles<\/a>. For instance, mine forces ASD-STE100 Simplified Technical English, so every reply reads like an aircraft maintenance manual: short sentences, simple words, active voice.<\/p>\n<pre class=\"line-numbers\"><code class=\"language-markdown\">---\nname: ASD-STE100\ndescription: Responses in ASD-STE100 Simplified Technical English\nkeep-coding-instructions: true\n---\n\nWrite all responses in ASD-STE100 Simplified Technical English.\n\n- Use the active voice.\n- Use the simple present, simple past, and simple future tenses. Do not use the -ing form of a verb.\n- Use a word with only one meaning. Use the simple word: \"start\", not \"commence\".\n- Write one instruction in each sentence.\n- Use a maximum of 20 words in an instruction. Use a maximum of 25 words in a description.\n- Write about one topic in each paragraph. Use a maximum of 6 sentences in a paragraph.<\/code><\/pre>\n<p>Why an output style instead of the global <code>CLAUDE.md<\/code>? They land in different places: an output style becomes part of the system prompt, and Claude Code periodically reminds the model to stick to it mid-conversation, while <code>CLAUDE.md<\/code> gets injected as a user message, where it competes with all your other rules. Plus, you can switch styles in <code>\/config<\/code> without touching your global rules.<\/p>\n<p>One gotcha: output styles apply to the main conversation only \u2014 a subagent runs its own system prompt, so your style doesn't change how subagents respond.<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/vinta\/hal-9000\/blob\/main\/dotfiles\/.claude\/output-styles\/asd-ste100.md\">GitHub: vinta\/hal-9000 - asd-ste100.md<\/a><\/li>\n<\/ul>\n<h2>Configurations<\/h2>\n<h3>Settings<\/h3>\n<p>There are some useful <a href=\"https:\/\/code.claude.com\/docs\/en\/settings\">configurations<\/a> you could set in your <code>~\/.claude\/settings.json<\/code>:<\/p>\n<pre class=\"line-numbers\"><code class=\"language-json\">{\n  \"$schema\": \"https:\/\/json.schemastore.org\/claude-code-settings.json\",\n  \"env\": {\n    \"CLAUDE_CODE_DISABLE_FEEDBACK_SURVEY\": \"1\",\n    \"CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS\": \"1\",\n    \"CLAUDE_CODE_RETRY_WATCHDOG\": \"1\",\n    \"DISABLE_ERROR_REPORTING\": \"1\",\n    \"DISABLE_EXTRA_USAGE_COMMAND\": \"1\",\n    \"DISABLE_FEEDBACK_COMMAND\": \"1\",\n    \"DISABLE_UPGRADE_COMMAND\": \"1\"\n  },\n  \"permissions\": {\n    \"allow\": [\"...\"],\n    \"deny\": [\"...\"],\n    \"ask\": [\"...\"],\n    \"defaultMode\": \"auto\"\n  },\n  \"model\": \"opus[1m]\",\n  \"effortLevel\": \"high\",\n  \"advisorModel\": \"fable\",\n  \"cleanupPeriodDays\": 365,\n  \"includeGitInstructions\": false,\n  \"showClearContextOnPlanAccept\": true,\n  \"teammateMode\": \"auto\",\n  \"voice\": { \"enabled\": true }\n}<\/code><\/pre>\n<p>Highlights:<\/p>\n<ul>\n<li><code>&quot;CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS&quot;: &quot;1&quot;<\/code>: Enable <a href=\"https:\/\/code.claude.com\/docs\/en\/agent-teams\">Agent Team<\/a> feature, a fancy way to consume a huge amount of tokens<\/li>\n<li><code>&quot;permissions.defaultMode&quot;: &quot;auto&quot;<\/code>: Use this to pretend it's safer than <code>--dangerously-skip-permissions<\/code><\/li>\n<li><code>&quot;advisorModel&quot;: &quot;fable&quot;<\/code>: Use something faster like <code>sonnet<\/code> as the main model, and let it <a href=\"https:\/\/code.claude.com\/docs\/en\/advisor\">consult<\/a> <code>fable<\/code> when needed<\/li>\n<li><code>&quot;includeGitInstructions&quot;: false<\/code>: Remove built-in commit\/PR instructions and git status snapshot from the system prompt, since my <a href=\"https:\/\/github.com\/vinta\/hal-9000\/blob\/main\/skills\/commit\/SKILL.md\">commit<\/a> skill covers that<\/li>\n<li><code>&quot;cleanupPeriodDays&quot;: 365<\/code>: By default, your chat history (location: <code>~\/.claude\/projects\/<\/code>) will be deleted after 30 days<\/li>\n<li><code>&quot;voice&quot;: { &quot;enabled&quot;: true }<\/code>: Enable <a href=\"https:\/\/code.claude.com\/docs\/en\/voice-dictation\">Voice Dictation<\/a> feature. Code like a boss!<\/li>\n<\/ul>\n<p>I turned all this settings-tweaking into a skill: <a href=\"https:\/\/github.com\/vinta\/hal-9000\/blob\/main\/skills\/audit-claude-settings\/SKILL.md\">audit-claude-settings<\/a> fetches the latest settings and env-vars from official docs, diffs them against your actual config, and suggests changes tied to how you work.<\/p>\n<p>The full settings I use:<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/vinta\/hal-9000\/blob\/main\/dotfiles\/.claude\/settings.json\">GitHub: vinta\/hal-9000 - .claude\/settings.json<\/a><\/li>\n<\/ul>\n<h3>Permissions<\/h3>\n<p>If you're not using a sandbox or devcontainer for Claude Code, you may want to block some <strong>evil<\/strong> commands in your <code>~\/.claude\/settings.json<\/code>:<\/p>\n<pre class=\"line-numbers\"><code class=\"language-json\">{\n  \"permissions\": {\n    \"defaultMode\": \"auto\",\n    \"deny\": [\n      \"Read(~\/.aws\/**)\",\n      \"Read(~\/.config\/**)\",\n      \"Read(~\/.docker\/**)\",\n      \"Read(~\/.dropbox\/**)\",\n      \"Read(~\/.gnupg\/**)\",\n      \"Read(~\/.gsutil\/**)\",\n      \"Read(~\/.kube\/**)\",\n      \"Read(~\/.npmrc)\",\n      \"Read(~\/.orbstack\/**)\",\n      \"Read(~\/.pypirc)\",\n      \"Read(~\/.ssh\/**)\",\n      \"Read(~\/*_history)\",\n      \"Read(~\/**\/*credential*)\",\n      \"Read(~\/Library\/**)\",\n      \"Edit(~\/Library\/**)\",\n      \"Read(~\/Dropbox\/**)\",\n      \"Edit(~\/Dropbox\/**)\",\n      \"Read(\/\/etc\/**)\",\n      \"Edit(\/\/etc\/**)\",\n      \"Bash(git -c *)\",\n      \"Bash(git --config-env*)\",\n      \"Bash(git --git-dir*)\",\n      \"Bash(gh repo delete *)\",\n      \"Bash(su *)\",\n      \"Bash(sudo *)\",\n      \"Bash(passwd *)\",\n      \"Bash(env *)\",\n      \"Bash(printenv *)\",\n      \"Bash(history *)\",\n      \"Bash(fc *)\",\n      \"Bash(eval *)\",\n      \"Bash(exec *)\",\n      \"Bash(rsync *)\",\n      \"Bash(sftp *)\",\n      \"Bash(telnet *)\",\n      \"Bash(socat *)\",\n      \"Bash(nc *)\",\n      \"Bash(ncat *)\",\n      \"Bash(netcat *)\",\n      \"Bash(nmap *)\",\n      \"Bash(kill *)\",\n      \"Bash(killall *)\",\n      \"Bash(pkill *)\",\n      \"Bash(chmod *)\",\n      \"Bash(chown *)\",\n      \"Bash(chflags *)\",\n      \"Bash(xattr *)\",\n      \"Bash(diskutil *)\",\n      \"Bash(mkfs *)\",\n      \"Bash(security *)\",\n      \"Bash(defaults *)\",\n      \"Bash(launchctl *)\",\n      \"Bash(osascript *)\",\n      \"Bash(dscl *)\",\n      \"Bash(networksetup *)\",\n      \"Bash(scutil *)\",\n      \"Bash(systemsetup *)\",\n      \"Bash(pmset *)\",\n      \"Bash(crontab *)\"\n    ],\n    \"ask\": [\n      \"Bash(curl *)\",\n      \"Bash(wget *)\",\n      \"Bash(open *)\",\n      \"Bash(* install *)\",\n      \"Bash(bun add *)\",\n      \"Bash(yarn add *)\",\n      \"Bash(pnpm add *)\",\n      \"Bash(uv add *)\",\n      \"Bash(git push *)\",\n      \"Bash(git remote add *)\",\n      \"Bash(git remote rename *)\",\n      \"Bash(git remote remove *)\",\n      \"Bash(git remote rm *)\",\n      \"Bash(git remote set-url *)\",\n      \"Bash(git config remote.*)\",\n      \"Bash(git config * remote.*)\",\n      \"Bash(gh repo create *)\",\n      \"Bash(gh repo rename *)\",\n      \"Bash(npx supabase db *)\"\n    ]\n  },\n  \"hooks\": {\n    \"PreToolUse\": [\n      {\n        \"matcher\": \"Bash\",\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \"python3 ~\/.claude\/hooks\/guard-bash-paths.py\"\n          }\n        ]\n      }\n    ]\n  }\n}<\/code><\/pre>\n<p>However, <code>&quot;deny&quot;: [&quot;Read(~\/.aws\/**)&quot;, &quot;Read(~\/.kube\/**)&quot;, ...]<\/code> alone is not enough, since Claude Code can still read sensitive files through the <code>Bash<\/code> tool. You can write a simple hook to intercept <code>Bash<\/code> commands that access blocked files, like this <a href=\"https:\/\/github.com\/vinta\/hal-9000\/blob\/main\/dotfiles\/.claude\/hooks\/guard-bash-paths.py\"><code>guard-bash-paths.py<\/code><\/a> hook.<\/p>\n<p>Though, <strong>Claude Code can still write a one-time script to read sensitive data<\/strong> and bypass all of the above defenses. So the safest approach is using sandbox after all.<\/p>\n<h2>Plugins<\/h2>\n<p>Claude Code <a href=\"https:\/\/code.claude.com\/docs\/en\/discover-plugins\">Plugins<\/a> are simply a way to package skills, commands, agents, hooks, and MCP servers. Distributing them as a plugin has the following advantages:<\/p>\n<ul>\n<li>Auto update (versioned releases)<\/li>\n<li>Auto hooks configuration (users don't need to edit their <code>~\/.claude\/settings.json<\/code> manually)<\/li>\n<li>Skills have a <code>\/plugin-name:your-skill-name<\/code> prefix (no more conflicts)<\/li>\n<\/ul>\n<p>To install a plugin, you need to add a marketplace first. A marketplace is usually just a GitHub repo. Think of it as a namespace.<\/p>\n<pre class=\"line-numbers\"><code class=\"language-bash\">\/plugin marketplace add openai\/codex-plugin-cc\n\/plugin marketplace add mattpocock\/skills\n\/plugin marketplace add vinta\/hal-9000\n\n# browse plugins\n\/plugin<\/code><\/pre>\n<p>Recommended:<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/anthropics\/claude-plugins-official\">GitHub: anthropics\/claude-plugins-official<\/a>: The official Claude Code marketplace<\/li>\n<li><a href=\"https:\/\/github.com\/openai\/codex-plugin-cc\">GitHub: openai\/codex-plugin-cc<\/a>: OpenAI Codex's official plugin for Claude Code<\/li>\n<li><a href=\"https:\/\/github.com\/obra\/superpowers\">GitHub: obra\/superpowers<\/a>: The first skills I ever installed and still find them useful<\/li>\n<li><a href=\"https:\/\/github.com\/mattpocock\/skills\">GitHub: mattpocock\/skills<\/a>: This is my new favorite<\/li>\n<li><a href=\"https:\/\/github.com\/vinta\/hal-9000\/tree\/main\/skills\">GitHub: vinta\/hal-9000<\/a>: My personal skills<\/li>\n<\/ul>\n<h2>Skills<\/h2>\n<p><a href=\"https:\/\/code.claude.com\/docs\/en\/skills\">Skills<\/a> can contain executable scripts and hooks, not just Markdown. <strong>Use with caution!<\/strong> When in doubt, have your agent review them first.<\/p>\n<p>Here are skills I've used, mostly installed per project when needed:<\/p>\n<pre class=\"line-numbers\"><code class=\"language-bash\"># my skills\nnpx skills add https:\/\/github.com\/vinta\/hal-9000 --skill commit best-practices blindspot write-like-me refactor-agents-md -g\nnpx skills add https:\/\/github.com\/vinta\/dear-ai\n\n# workflow skills\nnpx skills add https:\/\/github.com\/mattpocock\/skills -g\n\n# writing skills\nnpx skills add https:\/\/github.com\/softaworks\/agent-toolkit --skill writing-clearly-and-concisely\nnpx skills add https:\/\/github.com\/hardikpandya\/stop-slop\nnpx skills add https:\/\/github.com\/shyuan\/writing-humanizer\n\n# doc skills\nnpx skills add https:\/\/github.com\/upstash\/context7 --skill find-docs -g\n\n# LLM API skills\nnpx skills add https:\/\/github.com\/openai\/skills\nnpx skills add https:\/\/github.com\/google-gemini\/gemini-skills\n\n# language skills\nnpx skills add https:\/\/github.com\/trailofbits\/skills --skill modern-python\nnpx skills add https:\/\/github.com\/trailofbits\/skills-curated --skill python-code-simplifier\nnpx skills add https:\/\/github.com\/dagster-io\/skills --skill dignified-python\nnpx skills add https:\/\/github.com\/wdm0006\/python-skills\n\n# backend skills\nnpx skills add https:\/\/github.com\/vintasoftware\/django-ai-plugins\nnpx skills add https:\/\/github.com\/google\/skills\nnpx skills add https:\/\/github.com\/cloudflare\/skills\nnpx skills add https:\/\/github.com\/supabase\/agent-skills\nnpx skills add https:\/\/github.com\/planetscale\/database-skills\n\n# frontend skills\nnpx skills add https:\/\/github.com\/vercel-labs\/agent-skills\nnpx skills add https:\/\/github.com\/vercel-labs\/next-skills\n\n# design skills\nnpx skills add https:\/\/github.com\/openai\/skills --skill frontend-skill\nnpx skills add https:\/\/github.com\/pbakaus\/impeccable\nnpx skills add https:\/\/github.com\/nextlevelbuilder\/ui-ux-pro-max-skill\nnpx skills add https:\/\/github.com\/Leonxlnx\/taste-skill\n\n# seo\/aeo skills\nnpx skills add https:\/\/github.com\/warpdotdev\/oz-skills --skill seo-aeo-audit\n\n# video skills\nnpx skills add https:\/\/github.com\/remotion-dev\/skills\n\n# browser skills\nnpx skills add https:\/\/github.com\/microsoft\/playwright-cli --skill playwright-cli -g\n\nnpx skills list -g\nnpx skills update -g\nnpx skills remove --all -g<\/code><\/pre>\n<p>Recommended:<\/p>\n<ul>\n<li><code>\/brainstorming<\/code> from <a href=\"https:\/\/github.com\/obra\/superpowers\">superpowers<\/a>: When in doubt, start with this skill<\/li>\n<li><code>\/wayfinder<\/code> from <a href=\"https:\/\/github.com\/mattpocock\/skills\">mattpocock<\/a>: Let AI ask you a lot of questions<\/li>\n<li><code>\/find-docs<\/code> from <a href=\"https:\/\/github.com\/upstash\/context7\">context7<\/a>: Find the latest documentations<\/li>\n<li><code>\/frontend-design<\/code> from <a href=\"https:\/\/github.com\/pbakaus\/impeccable\">impeccable<\/a>: The better version of the official <code>\/frontend-design<\/code> skill<\/li>\n<li><code>\/simplify<\/code>: Run it often, you will like it<\/li>\n<\/ul>\n<p>You can find more skills on <a href=\"https:\/\/skills.sh\/\">skills.sh<\/a>.<\/p>\n<h2>MCP Servers<\/h2>\n<p>You probably don't need any <a href=\"https:\/\/code.claude.com\/docs\/en\/mcp\">MCP servers<\/a> if you can do the same thing with CLI + skills.<\/p>\n<h2>Context7 MCP<\/h2>\n<p>No, just use the <code>ctx7<\/code> CLI with <code>find-docs<\/code> skill instead.<\/p>\n<pre class=\"line-numbers\"><code class=\"language-bash\">npx ctx7 setup<\/code><\/pre>\n<ul>\n<li><a href=\"https:\/\/github.com\/upstash\/context7\">GitHub: upstash\/context7<\/a><\/li>\n<\/ul>\n<h3>Playwright MCP<\/h3>\n<p>No, you should use the <code>playwright-cli<\/code> skill instead. Both tools support <strong>headed mode<\/strong> (the opposite of headless), if you'd like to see the browser.<\/p>\n<pre class=\"line-numbers\"><code class=\"language-bash\">npm install -g @playwright\/cli\nnpx skills add https:\/\/github.com\/microsoft\/playwright-cli<\/code><\/pre>\n<ul>\n<li><a href=\"https:\/\/github.com\/microsoft\/playwright-cli\">GitHub: microsoft\/playwright-cli<\/a><\/li>\n<\/ul>\n<h3>GitHub MCP<\/h3>\n<p>No, you should use the <code>gh<\/code> command instead.<\/p>\n<pre class=\"line-numbers\"><code class=\"language-bash\">brew install gh<\/code><\/pre>\n<h3>Codex MCP<\/h3>\n<p>Yes, ironically. Other coding agents like Claude Code can use Codex via MCP, which is slightly more stable than directly invoking it with <code>codex exec<\/code> via CLI.<\/p>\n<pre class=\"line-numbers\"><code class=\"language-bash\"># Codex reads your local .codex\/config.toml by default\nclaude mcp add codex --scope user -- codex mcp-server<\/code><\/pre>\n<ul>\n<li><a href=\"https:\/\/developers.openai.com\/codex\/cli\/reference\/#codex-mcp-server\">Codex Command Line Options - codex mcp-server<\/a><\/li>\n<\/ul>\n<p>However, since OpenAI releases the official Claude Code plugin: <a href=\"https:\/\/github.com\/openai\/codex-plugin-cc\">codex-plugin-cc<\/a>, you should probably use that instead.<\/p>\n<h2>Hooks<\/h2>\n<p>Both <a href=\"https:\/\/code.claude.com\/docs\/en\/hooks\">Claude Code<\/a> and <a href=\"https:\/\/learn.chatgpt.com\/docs\/hooks\">Codex<\/a> support hooks. Hooks make Claude Code run specific commands on lifecycle events like <code>SessionStart<\/code>, <code>UserPromptSubmit<\/code>, and <code>PreToolUse<\/code>.<\/p>\n<p>Instead of reminding Claude Code to run the linter or tests in your prompts (and it still forgets sometimes), just write a <code>PostToolUse<\/code> hook that runs deterministically:<\/p>\n<pre class=\"line-numbers\"><code class=\"language-json\">{\n  \"hooks\": {\n    \"PostToolUse\": [\n      {\n        \"matcher\": \"Write|Edit\",\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \"uv run ruff check .\",\n            \"if\": \"Edit(**\/*.py)\",\n            \"timeout\": 30,\n            \"statusMessage\": \"Linting Python code...\"\n          }\n        ]\n      }\n    ]\n  }\n}<\/code><\/pre>\n<p>I also wrote some Claude Code plugins that use hooks:<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/vinta\/hal-9000\/tree\/main\/plugins\/hal-session-auto-rename\">GitHub: vinta\/hal-9000 - hal-session-auto-rename<\/a>: Automatically name each session and rename it as the conversation evolves<\/li>\n<li><a href=\"https:\/\/github.com\/vinta\/hal-9000\/tree\/main\/plugins\/hal-voice\">GitHub: vinta\/hal-9000 - hal-voice<\/a>: Play HAL 9000 voice clips on Claude Code hook events<\/li>\n<\/ul>\n<p>For example, <a href=\"https:\/\/github.com\/vinta\/hal-9000\/blob\/main\/plugins\/hal-session-auto-rename\/scripts\/hal-session-auto-rename.py#L50\">hal-session-auto-rename<\/a>. Now that Claude can <a href=\"https:\/\/code.claude.com\/docs\/en\/cross-session-messaging\">message your other Claude Code sessions<\/a> by name, a good session name actually matters. I found Claude Code already auto-names every session in the transcripts, so I just wired that up to a <code>UserPromptSubmit<\/code> hook, which can set <code>sessionTitle<\/code>.<\/p>\n<h2>Useful Tips<\/h2>\n<h3>Prompt Best Practices<\/h3>\n<ul>\n<li><a href=\"https:\/\/platform.claude.com\/docs\/en\/build-with-claude\/prompt-engineering\/claude-prompting-best-practices\">Claude Prompting Best Practices<\/a><\/li>\n<li><a href=\"https:\/\/developers.openai.com\/api\/docs\/guides\/latest-model\">Codex Prompting Best Practices<\/a><\/li>\n<\/ul>\n<h3>Command Aliases<\/h3>\n<pre class=\"line-numbers\"><code class=\"language-bash\"># in ~\/.zshrc\nalias cc=\"claude\"\nalias ccf=\"claude --model fable --effort max\"\nalias cct='tmux -CC new-session -s \"claude-$(date +%s)\" claude --teammate-mode tmux'\nalias ccy=\"claude --dangerously-skip-permissions\"\nccp() { claude --no-chrome --no-session-persistence -p \"$*\"; }<\/code><\/pre>\n<p>Use <code>ccp<\/code> for ad-hoc prompts:<\/p>\n<pre class=\"line-numbers\"><code class=\"language-bash\">ccp \"commit\"\nccp \"list all .md in this repo\"<\/code><\/pre>\n<ul>\n<li><a href=\"https:\/\/github.com\/vinta\/hal-9000\/blob\/main\/dotfiles\/.zshrc\">GitHub: vinta\/hal-9000 - .zshrc<\/a><\/li>\n<\/ul>\n<h3>Customize Your Statusline<\/h3>\n<p>Claude Code has a customizable <a href=\"https:\/\/code.claude.com\/docs\/en\/statusline\">statusline<\/a> at the bottom of the terminal. You can run any script that outputs text.<\/p>\n<p>Mine shows the current model, the current working folder, the git branch, and a grammar-corrected version of my last prompt (because my English needs all the help it can get). The grammar correction runs an ad-hoc <code>claude<\/code> command inside the statusline script.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/raw.githubusercontent.com\/vinta\/hal-9000\/main\/assets\/claude-code-statusline-grammar-check.png\" alt=\"Claude Code Statusline with English Grammar Check example\" \/><\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/vinta\/hal-9000\/tree\/main\/plugins\/hal-statusline\">GitHub: vinta\/hal-9000 - statusline<\/a><\/li>\n<\/ul>\n<h3>Run Ad-Hoc Claude Commands Inside Scripts<\/h3>\n<p>You can invoke <code>claude<\/code> as a one-shot CLI tool from hooks, statusline scripts, CI, or anywhere else. The trick is using the right flags to get a clean, isolated call with zero side effects:<\/p>\n<pre class=\"line-numbers\"><code class=\"language-python\">cmd = \"\"\"\n    claude\n    --model haiku\n    --max-turns 1\n    --setting-sources \"\"\n    --tools \"\"\n    --disable-slash-commands\n    --no-session-persistence\n    --no-chrome\n    --print\n\"\"\"\n\nresult = subprocess.run(\n    [*shlex.split(cmd), your_prompt],\n    capture_output=True,\n    text=True,\n    timeout=15,\n    cwd=\"\/tmp\",\n)<\/code><\/pre>\n<p>What each flag does:<\/p>\n<ul>\n<li><code>--setting-sources &quot;&quot;<\/code>: don't load hooks (avoids infinite recursion if called from a hook)<\/li>\n<li><code>--no-session-persistence<\/code> and <code>cwd=&quot;\/tmp&quot;<\/code>: avoid polluting your current context<\/li>\n<li><code>--tools &quot;&quot;<\/code>: no file access, no bash, pure text in\/out<\/li>\n<li><code>--no-chrome<\/code>: skip the Chrome integration<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>I've used Claude Code daily since it came out. Here are the best practices, tools, and configuration patterns I've picked up. Most of this applies to other coding agents (Codex) too.<\/p>\n","protected":false},"author":1,"featured_media":904,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[97],"tags":[127,153,101,154],"class_list":["post-903","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-about-ai","tag-chat-bot","tag-claude-code","tag-cli-tool","tag-coding-agent"],"_links":{"self":[{"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/posts\/903","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/comments?post=903"}],"version-history":[{"count":0,"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/posts\/903\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/media\/904"}],"wp:attachment":[{"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/media?parent=903"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/categories?post=903"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/tags?post=903"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}