Skip to content

Notch So Good

A pixel-art crab called Chawd lives in the MacBook notch and watches your coding agent — a timer while it works, a colour-coded notification when it needs you, and Allow / Deny buttons when it asks permission.

Screenshot of Notch So Good
Editor screenshot, 29 Sep 2026Notch So Good ↗

What it is

A macOS menu-bar app that turns the MacBook notch into a status display for a running coding agent. While Claude Code or OpenAI Codex CLI works, a black pill extends the notch with an animated pixel-art crab and a live session timer; when the agent needs input the notch expands into a colour-coded notification that jumps back to the owning terminal window, and permission requests are answered with Allow / Always Allow / Deny buttons or the shortcuts ⌃⌥A and ⌃⌥D. It reads the agent’s own local login to show session and weekly rate-limit bars, and it talks to no server of its own.

Who built itA solo developer whose GitHub account dates to 2016 and had one public follower when this record was written. He wrote all 87 commits, opened all eleven pull requests against his own repository, cut all sixteen releases, and shipped the result three ways — a Homebrew cask in his own tap, an npm package, and a curl | bash script — with self-updating builds behind them.

How it is put together

The parts · 6

A single SwiftUI menu-bar application sitting beside a small Python bridge, joined by a Unix domain socket at /tmp/notchsogood.sock and one shared vocabulary of events. The division of labour is the design: the app owns state and presentation, the bridge owns the conversation with the agent and decides nothing, and because Claude Code hooks are bidirectional the permission path runs the other way — the hook blocks on the socket until the notch answers, so a permission is a request and a response rather than a notification. Everything the app knows about a session arrives as an event and everything it shows is derived from those events, which is why the pill, the expanded notification and the menu-bar popover are three views of one session list instead of three code paths. There is no server and no account: rate limits are read from the user’s own local logins — Claude Code’s OAuth credentials in the Keychain, Codex CLI’s ~/.codex/auth.json — and the one optional network call is telemetry that stays off unless a build supplies a key.

NotchSoGood/ — the app
One Swift package (Package.swift, macOS 14+) split into Models/ (SessionStatus, NotificationType, PermissionMode, AgentSource), Services/ (NotificationManager at 38 KB, PermissionServer at 26 KB, SessionFileWatcher, UsageLimitsStore, UsageLimitsParser, StatsStore, Telemetry), Views/ (six files, of which SessionPillView.swift alone is 76,007 bytes), Windows/ (NotchPanel and a 20 KB NotchWindowController) and Utilities/ (NotchGeometry, NotchShape, PillLayout, MotionTokens, ProcessTree, WindowMatcher, TerminalLauncher, DisplayRouter, HotkeyManager).
HookInstaller/ — the bridge
hook.py at 15 KB is the only thing the agent ever runs; four shell scripts install it (install-hooks.sh for Claude Code, install-codex-hooks.sh for Codex, install-all-hooks.sh for every detected agent, get.sh for the curl path) and two test programs cover it, test_hook.py and integration_test.py. The bridge is read-only with respect to state: it forwards an event, and for PreToolUse it waits.
The socket and the event set
Claude Code receives ten events — nine fire-and-forget at a five-second timeout (SessionStart, SessionEnd, Stop, Notification, UserPromptSubmit, PreCompact, SubagentStart, SubagentStop, PostToolUse) and PreToolUse at 130 seconds so it outlasts the app’s 120-second decision window. Codex receives five (SessionStart, Stop, UserPromptSubmit, PreToolUse, PostToolUse) written into ~/.codex/hooks.json, which also requires enabling the codex_hooks flag in config.toml. Every event carries the agent’s live permission mode, which is what stops the notch from gating a session twice.
Tests/ and run-tests.sh
Six checks and no XCTest. Each suite is a plain main.swift compiled with swiftc against exactly the source files it covers — ElapsedFormatter, ProcessTree with WindowMatcher, UsageLimitsParser, and DisplayRouter with NotchGeometry and MascotView — plus the Python hook tests and a release build asserted to produce no warnings. The usage-limit parser is exercised against a recorded API response in Tests/Fixtures/oauth-usage.json rather than a live call.
Packaging and updates
Casks/notch-so-good.rb for the Homebrew tap, npm/install.mjs for the npx path, install.sh, uninstall.sh, build-app.sh, build-universal.sh, release.sh, generate-icon.swift and an appcast.xml for Sparkle. Three install routes, one build pipeline, and version numbers that appear in the app, the cask, the npm manifest and the appcast.
Repository-level agent material
CLAUDE.md is 2,858 bytes and its longest section is a design brief — users, brand voice ("weird, warm, technically precise"), aesthetic direction with named references and anti-references, and five design principles the first of which is a hardware-native feel. Alongside it sit plans/, skills-lock.json with the thirty symlinks, a committed QA report under .gstack/qa-reports/, the Product Hunt copy under PH/, and an llms.txt.

Choices, and what they beat

  • A Unix domain socket instead of a localhost TCP port over the TCP port 27182 the first versions listened on

    The v3.0.0 pull request gives both reasons in one line — faster, and "more secure (file-system permissions)".

  • The hook command is a real Python file over a JSON-escaped one-liner embedded in the settings file

    The installer’s own comment: "the escaped form was unreadable, untestable, and easy to corrupt". The rewrite is announced in the same release note as the correction it exposed — hook timeouts that had been 1000× too large.

  • Timeouts in seconds, with PreToolUse given 130 of them over treating the value as milliseconds

    The installer documents that Claude Code multiplies the number by 1000 internally, and that PreToolUse "has to outlast the app’s own 120s decision window" — the two facts that the earlier, wrong value had obscured.

  • Rate limits are read from the user’s own local logins over an account, a server, or a proxied API call

    The README states "No cloud, no accounts" and the limits are parsed from Claude Code’s Keychain credentials and Codex’s ~/.codex/auth.json. The Keychain read was moved to Apple’s own /usr/bin/security tool specifically "so one Always Allow survives every update", in place of a fresh prompt on every release.

  • No second prompt in bypass, auto or plan mode over letting the notch ask whenever a hook fires

    Those modes mean the agent is already deciding for itself, and because PreToolUse is bidirectional the extra prompt blocked the tool rather than merely duplicating a decision. acceptEdits likewise skips prompts for file edits, and a badge on the session row shows the mode in force.

Read fromThe README’s "How It Works" section, the pull request bodies for v3.0.0, v3.1.0, v4.0.0, v4.3.0 and v4.4.0, the release notes for 4.4.0 and 4.5.0, CLAUDE.md, run-tests.sh, plans/README.md, skills-lock.json, HookInstaller/install-hooks.sh and the repository tree.

Build log

7 stages
  1. 01

    Five months, sixteen releases, seven stars

    The repository was created on 2026-03-12 with a first commit titled "Initial release — Dynamic Island for Claude Code", and over the five months to 2026-08-13 it accumulated 87 commits and sixteen tagged releases running from v1.1.0 to v4.5.0 — every one of them a full release rather than a prerelease. Distribution exists in three forms: a Homebrew cask, an npm package whose only job is to fetch the app, and a shell script, with Sparkle handling updates after that. The commit curve is not flat: 62 commits in March, 7 in April, none in May or June, 12 in July and 6 in August, and the last of them is an appcast update for 4.5.0 rather than a code change. On 2026-09-30 the repository reports 7 stars, zero forks, zero watchers and one contributor against an app that is packaged, notarised and sixteen releases deep. Of the 87 commits, 82 carry the author’s GitHub account and five carry a MacBook-local email address instead. 74 commits name a Claude model as co-author — Opus 4.6 forty times, Opus 4.6 with a million-token context twenty-three, Fable 5 six, Opus 5 with the same context five — and three of those were written with literal \n escapes inside a single-line message, so on GitHub the whole commit reads as one line and the trailer never registers as one.

  2. 02

    A QA report with a health score, and where its findings ended up

    On 2026-03-19 the repository gained .gstack/qa-reports/qa-report-notchsogood-2026-03-19.md, a fifteen-minute audit of v1.2.2 filed under a directory named for gstack, the delivery tool this archive records separately. It gives the build a health score of 62 out of 100 and counts one critical, three high, five medium and eleven low findings, with sub-scores of 85 for visuals, 70 for performance, 65 for UX and 60 for accessibility. The critical finding is that three of the animation demos — wave, walk and peek-a-boo — crashed the app with EXC_BAD_ACCESS (SIGSEGV), four crash reports produced during the run. The report traces it to DemoWindowController.open(), where timers created by MiniChawdView fire callbacks into a view hierarchy that is released during autorelease-pool drain, and notes that the crash is non-deterministic because it depends on whether a timer interval happens to line up with the deallocation. Twenty-four scripted tests passed and exactly three failed, all three of them the crashing demos. The recommendations have a traceable afterlife: the report asks for @MainActor on NotificationManager, and that annotation is on the class today; it asks for hover at 60 fps with debounce, and v4.0.0 replaced 30-fps polling with an event-driven path; it asks for input validation, VoiceOver labels and notification on the display the agent is running on, and v4.4.0 ships length clamping, VoiceOver labels on the pill, rows and buttons, and display-following as its headline item. The archive records the correspondence rather than a cause — the release notes do not say which recommendation moved which change.

  3. 03

    The permission prompt that blocked the tool it was asking about

    The body of the v4.4.0 pull request is a four-part bug report written as a release note, and its first part is the sharpest failure in the repository. Every Claude Code hook payload carries permission_mode, which the author confirmed by reading the 2.1.220 binary, where it sits in the base hook schema beside session_id and cwd. The app never read it, so a session already running in bypass mode still got a second prompt from the notch — and because PreToolUse is bidirectional, that prompt did not merely duplicate the agent’s decision, it blocked the tool. The fix approves bypassPermissions, auto and plan without asking, treats acceptEdits as covering file edits, suppresses permission-type notifications in those modes, and puts a badge on the session row showing which mode is in force. The same release fixed status accuracy traced to three separate causes — one of them a guard let idx = firstIndex(...) else { return } present in every handler, which silently dropped every event belonging to a session the app had not seen start — and replaced environment sniffing for the owning terminal with a walk of the hook’s process tree, which is what makes window targeting work under tmux, ssh and login shells. Project-local allow rules in a repository’s .claude/settings.local.json began being honoured alongside the global ones.

  4. 04

    Two installers, and one of them installed an older set of hooks

    The 4.5.0 notes record a quieter class of the same problem: install.sh, the from-source path, was "writing its own stale, pre-4.0 hook set", so a source install produced a different set of hooks from every other route into the app, and it now installs the same set as everything else. The hook layer itself had been rebuilt a release earlier, and the installer states the reason in a comment — hook commands "invoke a real Python file (hook.py) rather than a JSON-escaped one-liner — the escaped form was unreadable, untestable, and easy to corrupt". The rewrite is announced in the same breath as the correction it exposed: hook timeouts that had been 1000× too large. The installer now sets nine fire-and-forget events to a five-second timeout and PreToolUse to 130 seconds, because that event has to outlast the app’s own 120-second decision window. It refuses to proceed if hook.py fails to compile, backs up settings.json before editing it, and aborts if that file is not valid JSON — a hook that fails to parse would leave the agent’s tool calls hanging.

  5. 05

    Eleven pull requests, not one comment from another person

    All eleven pull requests were opened by the author against his own repository, and each carries exactly one comment, every one of them from a review bot: nine contain a block reading "Review failed — The pull request is closed", one is a rate-limit notice counting down ("Next review available in: 58 minutes", with the per-file billing stated in the same paragraph), and one produced a walkthrough of the change. No comment on any of the eleven comes from a human. The bodies read as release notes rather than as proposals, which fits a workflow where the pull request is a record of finished work: the v4.3.0 body ends "QA: release build clean, code-reviewed (one race found and fixed), signed zip sha-verified", and an earlier one reports "40/40 QA tests passing" for the permission-system rewrite that reads Claude Code’s own allow rules, its dangerous-mode settings and MCP tool names before deciding whether a prompt is warranted at all.

  6. 06

    Skills pinned by hash and fanned out to thirty tools

    Two things in the tree describe a practice the archive has not seen in this form elsewhere. skills-lock.json is a lockfile for skills as dependencies: four entries, each naming an upstream repository and a sha256 of the file — apple-design, improve-animations and review-animations from emilkowalski/skills, and linkedin-content from inferen-sh/skills. The real content of that last one is a 9,648-byte file at .agents/skills/linkedin-content/SKILL.md, and thirty symlinks named skills/linkedin-content point at it, one inside each of thirty different agents’ dot-directories: .adal, .agent, .augment, .claude, .codebuddy, .commandcode, .continue, .cortex, .crush, .factory, .goose, .iflow, .junie, .kilocode, .kiro, .kode, .mcpjam, .mux, .neovate, .openhands, .pi, .pochi, .qoder, .qwen, .roo, .trae, .vibe, .windsurf and .zencoder, plus one at the repository root. Elsewhere, plans/ holds five numbered animation plans, every one of them marked DONE, and its README fixes the order they have to be carried out in — "005 (tokens first — 001/003 reference them) → 001 → 002 → 003 → 004" — with one constraint written for parallel work: "001 and 002 touch NotchNotificationView.swift — execute sequentially, not in parallel".

  7. 07

    What it did to be found, and what that produced

    The repository carries the apparatus of a launch. llms.txt is 3,302 characters written for agents rather than for people, and PH/product-hunt-listing.md holds the tagline, the gallery captions, the maker comment and a template for the first reply to a comment — all of it committed to the repository before any launch. The author also submitted his own project to at least two curated lists, once under the heading "Add Notch So Good — macOS notch companion for Claude Code" and once describing it as a notch-based session monitor with a pixel-art companion. What that had produced by 2026-09-30 is 7 stars, no forks and no open issues. The niche is not empty: a project called TokenNotch, created on the same day as the 4.5.0 release, covers the same two agents from the usage side and describes its mascots as the official ones, and it stood at 18 stars when this record was written. The README leads with the mascot, and the star count is the only evidence the archive has about what an audience did with it.

Adjacent records

All records →