Borrowing it
Nothing to install: this file belongs to Ideaplaces/agent-inbox. Take a copy, put it at the same path in your own repository, and replace the rules that are about this project with yours.
curl -O https://raw.githubusercontent.com/Ideaplaces/agent-inbox/main/CLAUDE.mdgit clone --depth 1 https://github.com/Ideaplaces/agent-inboxWrote this? Show the measurements
A badge with what this costs and how it scanned, read live from this page, so it follows the numbers instead of freezing them. Markdown for a README, HTML for a documentation site or a project page.
[](https://agentmods.dev/instructions/ideaplaces/agent-inbox/claude-md)<a href="https://agentmods.dev/instructions/ideaplaces/agent-inbox/claude-md"><img src="https://agentmods.dev/badge/instructions/ideaplaces/agent-inbox/claude-md/github.svg" alt="Measured on agentmods" height="20"></a>Or the 80×15 button, for a site that already has a row of RSS and ATOM ones. Only the verdict fits; the numbers stay here.
<a href="https://agentmods.dev/instructions/ideaplaces/agent-inbox/claude-md"><img src="https://agentmods.dev/badge/instructions/ideaplaces/agent-inbox/claude-md.svg" alt="Reviewed on agentmods" width="80" height="20"></a>What it costs to keep this loaded
Counted locally with the o200k_base tokenizer, which is exact for GPT models; Claude uses its own tokenizer and its counts differ. Treat this as one consistent yardstick across the catalogue rather than a bill. Prices are per million input tokens.
| Model | Per session | Once invoked |
|---|---|---|
| Fable 5.1 | $0.06092 | $0.06092 |
| Opus 5.5 | $0.02437 | $0.02437 |
| Sonnet 5.5 | $0.01218 | $0.01218 |
| Haiku 4.5 | $0.00609 | $0.00609 |
Grade B, and why
agent-inbox CLAUDE.md scanned grade B with 2 findings against 26 rules in 11 categories — prompt injection, anti-refusal, data exfiltration, privilege escalation, supply chain, agent snooping, system-prompt leakage, SSRF and excessive agency — measured 10d ago.
A static scan of the body, not an audit. Every finding is printed with the line that produced it so you can judge whether it matters here. A mod is markdown that instructs an agent; that is exactly why what it instructs is worth reading.
Reads agent configuration directoriesmediumAgent snooping
.claude/, .codex/, .gemini/ hold keys, settings and other credentials a mod has no legitimate need for.
`~/.claude/settings.json` and the Machines screenshot carried a warning about Makes network callslowCapability
Not a fault in itself. Listed so you know the mod talks to something, and to what.
notify jobs in `ci.yml` and `release.yml` are plain curl now, and a missing How it starts
The opening of the file, as written. The whole thing — 387 lines — stays where its author put it; the contents beside it link to each section on GitHub.
Agent Inbox: working notes
The Mac app is in mac/, the senders are bash at the repo root. mac/README.md
has the architecture and the design decisions; this file is what someone
changing the code needs to know.
Release credentials, the signing certificate and the machine that builds the DMG are deliberately not here. They are IdeaPlaces-internal and this repository is public.
Local development
cd mac
swift test # no network, no side effects
./build.sh --debug # ad-hoc signed, host architecture, fine for running yourself
SIGN_IDENTITY="Developer ID Application: ..." ./build.sh
Copying a local build over /Applications replaces the released one, so brew's
recorded version will disagree with what is installed until the next upgrade.
Harmless, but it explains the mismatch.
The senders are testable without a transport:
./test-notify.sh # runs notify.sh against a throwaway HOME, prints instead of posting
Gotchas already paid for
Each of these shipped a release that looked healthy and was not. The full write-up, including the fixes, is at docs.ideaplaces.com/devops/macos-app-signing.
- The build number has to rise. Sparkle compares
CFBundleVersion, not the marketing version. It came fromgit rev-list --count HEAD, which is1on CI because checkout clones to depth 1, so every release shipped build 1 and no update was ever offered. Now derived from the version. --timestamp=nonefails notarization. Ad-hoc signing requires it and Apple rejects it, so branch on the identity.- Staple the app, not only the DMG. A ticket on the image does not travel with
the app into
/Applications, leaving a Mac that is offline at first launch with nothing to fall back on. --deepis wrong once a framework is embedded. Sign inner-out: Sparkle's XPC services,Updater.app,Autoupdate, the framework, then the app.- Library validation needs matching Team IDs. An ad-hoc signature has none, so a local build dies loading Sparkle. Ad-hoc builds use a separate entitlements file that relaxes it; never ship that file.
hdiutil create -srcfolderneeds a writable mount. Managed Macs and CI runners force disk images read-only. Usemakehybrid.- Apple's timestamp server fails under load.
A timestamp was expected but was not found.broke the v0.1.5 release on a Sparkle XPC service. The timestamp is mandatory for notarization, so signing retries with backoff rather than failing the release. - Build against the SDK the app will run on. 0.1.8 was built on the hosted
macos-14image, so it links against the macOS 14.5 SDK. On macOS 26 that binary grows itsMenuBarExtrawindow and never shrinks it again: the window stays at the tallest list of the session (measured at 560x472) while the content draws its real height, and SwiftUI centres the content in the leftover space. The result is a menu floating ~140pt below the menubar with dead margin above and below, which reads as a padding bug and is not one. The same source built with the macOS 26 SDK goes 652 -> 157 across the same shrink. Check withvtool -arch arm64 -show-build <binary> | grep sdkbefore chasing a layout problem that only appears on one Mac. - The SDK stamp is set by
build.shand checked, because the toolchain stopped setting it. SwiftPM under Xcode 26 stamped the real SDK. Under Xcode 27 it stamps the deployment target, so a machine with the 27.0 SDK producedsdk 14.0binaries and 0.1.32, 0.1.33 and 0.1.34 all floated. Nothing else showed it: builds passed, tests passed, screenshots matched. The stamps tell the story on their own: 0.1.30 and 0.1.31 read26.5, the next three read14.0, and Xcode was updated in between.build.shpasses-platform_version macos 14.0 <sdk>to the linker and then runsmac/check-sdk-stamp.sh, which fails unless every slice is stamped at least as new as the machine's own macOS;verify-dmg.shruns the same check on the image, in CI and before a release publishes anything. If a future toolchain ignores the flag, the build fails instead of shipping. - The build Mac's toolchain is recorded, and a change turns CI red. Xcode
updates itself on the runner and nothing in the repo changes when it does.
mac/TOOLCHAINholds the Xcode version, its build and the macOS SDK;mac/check-toolchain.sh --checkruns first in CI and in the release, and a mismatch fails with the procedure: build, package,verify-dmg.sh,screenshots.sh --check, open the menu once with your eyes, then--writeand commit. Red here means "look", not "broken". The alternative was learning about Xcode 27 from a user's screenshot three releases later. - A note in this file once blamed the layout for that, and was wrong. It
said 0.1.33 floated "on the current SDK", which nobody had checked with
vtool; it was stamped 14.0.WindowFitterwas written on that belief. It stays as a second layer, but the version that shipped in 0.1.34 could not have worked: it fitted only when the measured height changed and skipped a window not yet on screen, and a menu reopening at a stale height is both.FittingViewnow also fits whenever its window becomes key or changes occlusion, andWindowFitterTestsreproduces the stale-window case against a realNSWindow. Before believing a claim about which SDK a build used, read the stamp. - A Focus profile silences notifications and nothing in the API says so.
UNUserNotificationCenterreportedauthorized,alert: enabled,sound: enabled; audio output was fine; and"Pop","Pop.aiff"and.defaultwere all silent. A Focus mode the app cannot see was filtering it. Before chasing a sound name or a permission, ask whether Focus is on: the app has no way to detect it andadd()reports success either way. - The hook runs
~/.agent-inbox/bin/notify.sh, not your checkout. The app unpacks it from the bundle at launch, so a fix in the repo does nothing until a build carrying it is installed. A local build made before the fix will happily overwrite the good copy. Check withgrepagainst the installed file, not the one you edited. - A file left by a transport you stopped using keeps sending.
notify.shposts to whatever it finds in~/.agent-inbox/, so switching the app from Discord to ntfy left every session publishing to both for days with nothing to show it. The writer now retires the other transport's files. - The DMG window comes from a committed
.DS_Store. Without one, Finder opens the image at whatever size and icon scale it last used, which is the cramped default window. The usual way to make one is to mount the image read-write and drive Finder over AppleScript, and this project cannot: that is the same read-only-mount problem that madecreate -srcfolderunusable.mac/dmg/make-ds-store.pywrites the file directly, with no mount and no GUI, andmakehybriddoes carry dotfiles into the image. Re-run it only when the layout changes, and note that the names in it must match the staged files exactly or Finder silently falls back to automatic positions. - Finder drops a view-options dictionary it does not recognise, whole. The
0.1.24 image asked for 128pt icons and got Finder's default 64: the window
settings in
bwspapplied while every setting inicvpwas discarded, with no error and nothing in a log. The file carried two keys Finder never writes (scrollPositionX/scrollPositionY, plus a strayICVO) and was missing three it always writes (backgroundColorRed/Green/Blue). Match the key set of a.DS_StoreFinder itself produced, which means pulling one out of a shipping DMG and diffing against it. - The background picture needs the
pBBkbookmark, not only the alias.backgroundImageAliasinsideicvpis the older half and Finder does not resolve it on its own. Both are written now. - AppleScript cannot tell you whether the background applied.
background picture of icon view optionsreportsNONEfor images that certainly have one, checked against a third-party DMG as a control before trusting the reading.icon sizeand the window bounds do read back correctly, so verify what can be verified and have a person look at the rest. - The wire has a contract now, and two ways a message can reach the parser.
notify.shappends a versioned JSON line after the human lines and the old footer.Transport.splitFooterpeels the last body line only if it looks like a footer, so normally the JSON stays in the body and the footer arrives inside it; but if any contract string contains· /,splitFooterpeels the JSON intomessage.footerinstead.MessageParserchecks both places. Anyone rewritingsplitFooterneeds to keep both arrangements working, andMessageParser.peelFooterduplicates its two conditions on purpose so the model layer does not import the transport: change one, change both. - A contract with a null or unknown
kindfalls back whole. Half-applying a contract is worse than ignoring it, so the parser drops to the emoji heuristics for the entire message rather than filling what it can. closing_wordsnever contains a newline. Line ends are sentence ends by design. A test asserting a newline survives through.closingwill fail on the reduction, not on jq.HOST_LABELin the environment is ignored.notify.shassignshostname -sunconditionally before sourcing config, so only~/.agent-inbox/configcan set it.HOST_LABEL=x ./notify.shdoes nothing.- The dry-run output has no
footer:line any more. The footer is the second-to-last body line and the JSON contract the last.tail -1of a dry run is the contract. - Backoff after a failure starts at 2s, not 1s.
consecutiveFailuresis incremented before the sleep, so the ladder after failures is 2, 4, 8, 16, 32, 60.backoff(0), one second, is the wait after a healthy stream closes. Pinned inReceiverTests; change the loop and the test together. - The receiver's watchdog ends the connection; it used to only flag it. The
first streaming build set a flag at ten seconds but stayed inside the
for try awaituntil URLSession's 120s idle timeout threw, so the poll fallback began after two minutes, not ten seconds. Nothing caught it because nothing could: the loop calledTask.sleepdirectly. Making time injectable (Sleeper) is what surfaced it. Stream and watchdog are now siblings in a task group and the first to finish decides. receiver.restart()no longer restarts housekeeping. Reconnect and topic changes cycle the connection only; presence and expiry keep their own clock.- In receiver tests, settle on the last effect in the chain, not the first.
settle(until:)returns when its condition holds; state set one hop later may not be there yet. And two sleeps woken by oneadvanceresume in deadline order, but the main actor does not promise to run them in that order, so never assert cross-task ordering off a single advance. - Never use
UserDefaults(suiteName:)in a test. It writes a real plist into~/Library/Preferences, and cfprefsd rewrites it after the process has exited, soremovePersistentDomainintearDowndoes not stick. Two tests had left 826 of them on the maintainer's Mac before anyone looked. UseMemoryDefaultsfromIsolatedSettings.swift, and build models throughIsolatedSettings.model(), which is the one way a test gets an app model that touches nothing real. - Adding a setting is two places. A property on
SettingsValuesand a line in its hand-writteninit(from:). Forget the second and the field decodes as its default forever, silently, because the decoder is deliberately lenient so an older snapshot never fails to load. - The gate for "no singletons" is
static let shared, not.shared.grep -rn "\.shared" mac/Sourceswill always find Apple's own (NSWorkspace.shared,URLSession.shared). The one that means something isgrep -rn "static let shared\|static var shared" mac/Sources, which must print nothing. - Sender config writes are change-gated now. A setter used to rewrite
~/.agent-inbox/configunconditionally; nowsync()runs only when a sender-visible field actually changed.AppModel.start()still syncs once unconditionally, so a fresh launch always leaves the file current. - The delegate gets the model from the App, not from a global.
AgentInboxApp.initbuilds the model and setsdelegate.modelon the adaptor, which has already constructed the delegate by then; verified with a throwaway package rather than assumed. The optional is guarded with a precondition, so a future refactor that reorders this fails at launch loudly, which is the right failure. - The senders and the app are checked against each other on every push.
fixtures/wire/holds the sender's real dry-run output for seven scenarios, regenerated byfixtures/wire/regenerate.shwith every varying input pinned;test-notify.shfails if the committed files drift, andWireFixtureTestsdecodes every one throughsplitFooterthen the parser and asserts.contract. A new scenario is three edits:regenerate.sh, run it, and theexpectedlist in the Swift test, which asserts the directory holds exactly that set so a forgotten entry fails rather than being skipped. - Tests source
notify.sh, they do not cut functions out of it.AGENT_INBOX_SOURCE_ONLY=1 source notify.shreturns before the stdin read and the dispatch.AGENT_INBOX_NOWpins "now" for the same reason. Both are environment-only seams; config cannot set them. Under a normal run the top-levelreturnis a silent no-op, so the seam only means anything when sourced. - Tags inside code are ignored. An agent's report quoting
`#mute`muted the maintainer's session for hours.strip_codedrops fenced blocks and inline spans before matching; an unclosed fence swallows the rest of the prompt on purpose, biased toward not matching. Worth knowing when someone reports "my #notify did nothing". - The end-to-end test is the one that catches sender-to-app bugs.
EndToEndTestsruns the realnotify.shas a subprocess againstFakeNtfyServeron 127.0.0.1 and asserts a row in the store. It would have caught the"$BODY💬"expansion bug; the test was proven by putting that bug back and watching it fail. It skips only whenjqis missing. - Never build an
AppModelin a test withoutIsolatedSettings.model().UNUserNotificationCenter.current()aborts the whole test process in a bundle-less runner.IsolatedSettingspasses aRecordingPoster; a bareAppModel(settings:defaults:)that delivers an item takes every test down with it. - In
FakeNtfyServer, a dropped stream is a clean close on purpose. It writes the terminating chunk before cancelling, which is what makesstream()finish without throwing and the receiver reconnect on the 1s path. Cancelling without it is the "killed process" case: URLSession throws-1005, the receiver counts a failure and backs off 2s. Both are real ntfy behaviours; the tests exercise the first. settle()cannot wait across a socket. It yields a fixed number of times, which is microseconds. Anything waiting on URLSession or a subprocess useseventually(within:), a real-time deadline poll.- The installers refused nothing until they had tests. Both reported success
over a corrupt
settings.jsonbecauseset -edoes not fire inside an&&list;install.shwrote the hook path unquoted, so a checkout under a directory with a space produced hooks that exit 127 on every turn. Fifty-one checks intest-install.shnow, run in CI on Ubuntu and verified on macOS bash 3.2. Any change to how hooks are written must keepHookInstaller.swiftand both shell installers in agreement; the test'sapp_shape_okasserts it. - The README screenshots are generated, checked in CI, and re-committed by
every release.
mac/screenshots.sh --writeregeneratesdocs/*.pngfrom the views with every varying input pinned;--checkrenders into a temp directory and fails the push ifdocs/differs or the README links an image nothing generates. It runs only on the self-hosted Mac: the images are byte-exact to that machine's SDK and fonts. The version in the pictures is a fixed1.0.0on purpose, because the release runner rewritesVERSIONfrom the tag while the file in git stays behind, and a picture tracking either would fail the check on the first push after every release. - A settings capture must be sized to the view's own fitting height. Forcing a taller frame makes the scroll view fill the slack by scrolling and the picture comes out mid-page with the header and tabs cut off.
IsolatedSettingsredirectsHookInstaller.settingsURLtoo. It did not for a while, so an "isolated" model read this Mac's real~/.claude/settings.jsonand the Machines screenshot carried a warning about the maintainer's own hooks. Two statics are redirected into the scratch directory; a third one would be a test reading something real again.mac/verify-dmg.shreads a built image back the way Finder and Sparkle will, and runs in CI after packaging and in the release before publishing. A probe that pipes its output throughtailloses the exit code; call it bare. Stray/Volumes/Agent Inbox Nmounts on a dev Mac are from hand probing, not from it: it detaches in a trap on every path.- Local shellcheck says nothing about CI's. Ubuntu's runner ships 0.9.0, a current Mac 0.11. SC2120 (an argument nothing passes) exists only in the older one and turned every push red for a week while the Mac said clean. chipdev has 0.8.0, which is the stricter check: run a script through it before trusting a green local run.
- CI was red for a week and Discord never said so. The notify job called a
shared action that treats a missing webhook as "nothing to do" and exits
green, and the secret had never been set on this repo. Every failing run
ended with a green Discord job whose log said
no webhook configured. The notify jobs inci.ymlandrelease.ymlare plain curl now, and a missingDISCORD_WEBHOOK_AGENT_INBOX_PRODUCTIONfails the job instead of passing it. The channel is#agent-inbox-production, created byideaplaces-devops/discord/setup-cicd-notifications.sh; the webhook is in Key Vault asdiscord-webhook-agent-inbox-production. A notifier that cannot notify must go red, because nobody opens the log of a green job. - The Stop hook fires before the transcript has the turn's last message.
Measured on Claude Code 2.1.274: the file was seven lines short when the hook
started and complete 200ms later. Reading the transcript there finds the
previous message, so every row was one message in the past, most visibly on a
short turn with no tool calls. The payload's
last_assistant_messageis the turn that ended; the transcript is only the fallback for older versions and for the Notification hook. Never sleep-and-reread to fix this: the hook must not block a session. - A background agent's report is a prompt, and a turn. Its
<task-notification>firesUserPromptSubmitand thenStop, so before 0.1.36 every agent that finished cleared the session's row as if the user had read it and put up a fresh ✅: a session running five agents produced five rows, none with anything to do. The prompt branch now ignores those, and the stop branch holds whilerunning_agentsfinds launches with no report. The report reaches the transcript in two shapes, a user entry when the session was idle and aqueued_commandattachment when it was mid-turn; matching only the first left half of all agents looking out forever. The hold is the one thing in the sender that outlives the hook: a detachedrecheckprocess, guarded by a nonce instate/<id>.hold. It does not read the transcript late: the launch's tool result is written before the model streams its closing reply, so it is already there whenStopfires. - A view change lands red on
mainand stays red until the next real push. CI comparesdocs/*.pngbyte for byte against pictures rendered on the Mac, and the images can only be regenerated there (mac/screenshots.sh --write). A view change pushed from anywhere else fails that step; the release job then renders and commits the new pictures, but a commit pushed withGITHUB_TOKENnever triggers a workflow, so no green run follows and the Discord failure post stays up. The order that works from a Linux box: push the change (red), tag the release (pictures land), then push the next real commit tomain, which runs green on the fresh pictures and clears the post. - Test the binary you think you are testing. A
--configureflag appeared to hang through several rounds of debugging because/Applicationsheld the released build, which predated the flag.
What this file has done since we first saw it
Hashed on every crawl. A supply-chain change to an agent config is a question of when, not whether, so the history is kept rather than the latest state alone.
- 10d ago Changed · +13 lines · +238 tokens per session 4a30ee4a4fd3
- 1mo ago Changed · +8 lines · +153 tokens per session d2b8553ac84b
- 1mo ago Changed · +77 lines · +1,128 tokens per session 40909727dee3
- 1mo ago Changed · +83 lines · +1,487 tokens per session scan A → B b8a9940397b6
- 1mo ago Changed · +90 lines · +1,603 tokens per session 600311c743fb
- 2mo ago First seen · 116 lines · 1,483 tokens per session scan A a3972c9b8f52
agent-inbox CLAUDE.md is an instructions file published in the GitHub repository Ideaplaces/agent-inbox (3 stars, last pushed 10d ago), licensed MIT. It adds 6,092 tokens to every session, about $0.0244 per session on Opus 5.5. A static security scan graded it B with 2 findings (reads agent configuration directories, makes network calls). No closer match exists in the catalogue, so it is treated as the original; first seen 2026-08-31.
Other instructions, from other repositories
vscode buildNext.instructions.md
Working notes and architecture documentation for the new esbuild-based build system in build/next. Use when making changes to the new build pipeline (transpile/bundle commands, NLS plugin, source-map handling, resource copying, or self-hosting watch tasks).
next.js AGENTS.md
AGENTS.md instructions for vercel/next.js, covering next.js development guide, codebase structure, monorepo overview, core package: packages/next and other important packages.
vscode oss-third-party-notices.instructions.md
Instructions for microsoft/vscode, covering vs code oss third-party-notices pipeline, architecture, pipeline flow in ci, cache eligibility and rollout and applying the notice (cutover).
langchain AGENTS.md
AGENTS.md instructions for langchain-ai/langchain, covering global development guidelines for the langchain monorepo, corridor security analysis, project architecture and context, monorepo structure and development tools & commands.
vscode copilot-instructions.md
Copilot instructions for microsoft/vscode, covering vs code copilot instructions, project overview, root folders, core architecture (src/ folder) and built-in extensions (extensions/ folder).
deepseek-harness AGENTS.md
AGENTS.md instructions for deepseek-ai/deepseek-harness, covering agents.md, pre-stable apis and released session data, repository layout, commands and host sandbox failures.