harborclerk / macos
AGENTS.md3 starsChanged 31 days agoMITView on GitHub
Two Swift apps: Harbor Clerk Server (menubar agent managing every backend service) and Harbor Clerk (WKWebView shell around the SPA). Build scripts in scripts/, orchestrated by Makefile. - Never call proc.waitUntilExit() on the MainActor, nor unbounded anywhere. It stalls every service behind it, has parked forever on an exited child, and in a test tearDown escapes every XCTest timeout (#890). Use await withCheckedContinuation (as MigrationRunner does); in tests, killAndReap. - Nil the readabilityHandler on EOF. Log.createPipe() installs a readability handler;…
- Installs packages
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,439 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository9 other files it gives its agents.
- AGENTS.mdAGENTS.md
- alembic/AGENTS.mdAGENTS.md
- frontend/AGENTS.mdAGENTS.md
- acceptanceSkill
- benchmarkSkill
- build-macosSkill
- model-refreshSkill
- verifySkill
- harbor-clerkSkill
The file
We split this AGENTS.md into the rules it gives an agent
1 rule. About 0 are common, 1 is distinctive, and 0 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 86
When bumping, re-test on the target OS: the WKWebView loopback load, watched- folder access from the background watcher, and every spawned …
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
AGENTS.md — macos
Two Swift apps: Harbor Clerk Server (menubar agent managing every backend
service) and Harbor Clerk (WKWebView shell around the SPA). Build scripts in
scripts/, orchestrated by Makefile.
Process management — the rules that cost real outages
- Never call
proc.waitUntilExit()on the MainActor, nor unbounded anywhere. It stalls every service behind it, has parked forever on an exited child, and in a testtearDownescapes every XCTest timeout (#890). Useawait withCheckedContinuation(asMigrationRunnerdoes); in tests,killAndReap. - Nil the
readabilityHandleron EOF.Log.createPipe()installs a readability handler; if it is never cleared,waitUntilExit()deadlocks because the pipe's read end never closes. Sethandle.readabilityHandler = nilinside theguard !data.isEmptyelse branch. This presented as Alembic finishing while the runner hung forever;sample <pid>showed the thread parked inmach_msg2_trap. @MainActorasync functions still interleave. They can suspend at anyawait, so state read before an await may be stale after it. Guard every state transition against concurrent mutation —startService()must re-check.shutdownPendingrather than trusting an earlier check.- Kill process groups, not PIDs. Tika forks JVMs and workers spawn
pdftoppm,tesseract,magick.kill(pid, SIGKILL)orphans the grandchildren to launchd, where they survive relaunch. Usekillpg.
ServiceState has seven states: stopped, startupPending, starting,
running, shutdownPending, stopping, errored. Pending states render with
blue dots.
Postgres and Tika may be launchd-managed rather than direct subprocesses, so they survive a menubar crash.
Environment for subprocesses
ServiceManager builds one shared environment dict handed to every subprocess,
which is why macOS gets variables like LLAMA_SERVER_URL for free. Docker
Compose has no equivalent — each service opts in individually, and forgetting
one there caused summarize to silently produce extractive summaries. When adding
a variable a worker needs, check whether the Compose side needs it too.
Nothing a launched process runs may write into the bundle: it breaks the code
seal (numba did, #846). Every launch path ends with applyingRuntimeCacheRules
in ServiceManager, whose comments hold the cache rules; never bypass it.
Networking
The WKWebView app loads the SPA over plain HTTP on loopback at api_port
(default 8100). The HTTPS gateway is a separate Caddy surface on 8443
for local MCP clients requiring TLS, with internal or operator-supplied
certificates and a loopback-versus-LAN exposure check. These are different
surfaces; do not conflate them.
Port 80 is frequently occupied on macOS (AirPlay Receiver and friends) and is not needed for local development.
The client Info.plist declares no ATS keys, so the loopback cleartext load
depends on WebKit's implicit localhost allowance rather than a declared
exception. That is a known gap, not a decision — see #561.
Do not bump the SDK without re-testing
Many of macOS's stricter privacy and security defaults are gated "linked-on-or-after" the SDK a binary was built against. Rebuilding against a newer SDK is what flips the stricter rules on — ATS, TCC/file access, hardened-runtime and JIT policy can all change behavior with no source change at all. So an SDK bump is a behavioral change, not housekeeping.
The bump has already happened, and nothing recorded it: the deployment
target is still 15.0, but the SDK follows whatever Xcode is on the build machine:
Xcode 26 moved it, then the release machine's Xcode 27.1 (SDK 27.0): vtool
-show-build on both apps' executables, built 2026-10-17, gives minos 15.0, sdk
27.0. The stricter rules are already live in every build: SDK 26.5 builds passed
a full field test (11,436 documents, IMAP sync, search, Research), 27.0 builds have
not had one yet. The #561 ATS gap runs under the stricter regime today.
Two consequences:
- The risk is inverted. Building with an older Xcode now loosens the
shipped binary. The CI
macosjob runs onmacos-15, whose SDK is older than the release machine's, so CI validates a more permissive configuration than ships and cannot catch a regression only the stricter SDK triggers. - Nobody decided to bump; a routine Xcode upgrade did.
vtool -show-buildon the built binary is the check; pinning Xcode in CI and at release time is the fix, and is not yet done.
When bumping, re-test on the target OS: the WKWebView loopback load, watched-
folder access from the background watcher, and every spawned helper (Postgres,
the Tika JVM, llama-server, bundled Python). See the macOS 27 readiness
assessment in docs/superpowers/plans/ and epic #563.
Entitlements: restricted keys kill a Developer ID app silently
keychain-access-groups (and anything under com.apple.developer.*) is a
restricted entitlement. A Developer ID build that declares one without an
embedded provisioning profile is SIGKILLed by the kernel at exec, before
main(). Nothing in the pipeline notices: codesign --verify --deep --strict
passes, spctl --assess says accepted, and the notary service returns Accepted
— none of them run the binary. This shipped as a stapled DMG whose app exited
137 with no stderr. notarize.sh now exec-probes both apps before submitting.
Probe a copy, never the artifact. Launching a notarized bundle in place puts
it under App Management protection: from then on nothing — not even the Terminal
that signed it — may write inside it, and the next make sign on that output
dies re-signing tika-server.jar with "Operation not permitted". The fix is to
mv the bundle aside (a rename on the parent is allowed) and regenerate it with
package.sh. notarize.sh probes a ditto copy for exactly this reason.
Dev builds hid it for months: Xcode's automatic signing embeds a development profile that authorises the key. The release path never had one.
Also: on the file-based login keychain (the default without
kSecUseDataProtectionKeychain) kSecAttrAccessGroup is ignored on read and
write — measured — so the group never delivered the cross-rebuild ACL anchoring
it was added for. Stable Developer ID signing is what does that.
No native dialogs in the web view
window.confirm and window.alert silently return false in WKWebView. Swift
retains only two JS bridges — pickFolder (NSOpenPanel) and revealInFinder
(NSWorkspace). Folder management UI lives at /folders in the web app; resist
adding bridges for things the web app can do.
Naming
Bundle IDs, keychain service IDs, access-group suffixes, and Logger subsystems
all use com.harborclerk.* — never com.bitblot.*.
Worker presets (C = logical cores)
| Preset | io | cpu | llm |
| Quiet | 1 | 1 | 1 |
| Balanced | max(2, C//4) | 2 | 1 |
| Fast | max(2, C//2) | 3 | 1 |
Hard caps: io ≤ 8, cpu ≤ 4. The llm queue runs a single worker in every
preset. These are defined in ServiceManager.workerCounts.
Builds
build-venv.sh installs exactly what the root uv.lock pins (exported with
hashes), then the two local packages with --no-deps --upgrade, and rebuilds the
venv when the lock changes. Never add a bare pip install <name>: the app once
shipped torch two minors behind the lock, and it cost 43 GB of GPU memory (#685).
Every version string comes from the release tag. package.sh derives them
from git describe (scripts/build-version.sh): tag before you build, and
never bump the pbxproj's 0.0.0-dev sentinel (a test fails if you do). The
procedure and what each stamp means: docs/releasing.md.
The llama.cpp pin lives in two places
scripts/build-llama.sh (LLAMA_CPP_TAG) and the image in docker-compose.yml;
tests/test_llama_cpp_pin.py holds them together and to a stable release. Bump
both in one PR, on its own, and re-baseline every model after it. The build
replaces a clone an older pin left behind, fails unless the binary's --version
names the pinned commit, and fails on any link outside the bundle and the OS
(Homebrew's OpenSSL shipped that way for months: both build machines have it).
A model server does not run out of memory. The Mac does.
Unified memory lets llama-server allocate more than the machine has; the host
swaps until the kernel watchdog panics. The mini (32 GB) did so on 2026-09-17
when a hand-launched smoke test passed the YaRN-extended context (-c 131072
-np 2: ~19 GB of KV cache for Qwen3-8B at ~144 KB per token) beside the app's
own server holding gpt-oss-20b. Before launching any llama-server by hand,
add the model file to the KV cache, subtract what is already resident (the
app's server, anything else on the host that loads models), and stay well
inside what is free. YaRN is off by default; use the app's defaults.
cc can pick up the Command Line Tools SDK, not Xcode's
make apps compiles PostgreSQL and pgvector from source. Where a macOS beta
updated the Command Line Tools, the bare cc that configure runs resolves the
CLT SDK (/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk) even though xcode-select -p points at Xcode.app. When that SDK is
newer than Xcode's linker (CLT 27.0 against Xcode 26.5, pre-Xcode 27) every link fails with
ld: tapi error: malformed file
.../MacOSX27.0.sdk/usr/lib/libSystem.B.tbd:4:20: error: unknown architectureand the build stops at configure: error: C compiler cannot create executables.
Nothing in the repo changed; the OS update did it. Pin the SDK to Xcode's:
SDKROOT=$(xcrun --sdk macosx --show-sdk-path) make appsxcrun --show-sdk-path without --sdk macosx returns the CLT path there, so
use the explicit form. make sign compiles nothing and needs no override.
make sign over SSH: errSecInternalComponent
codesign finds the Developer ID identity and then fails on the first thing it
signs (a dylib inside tika-server.jar), because an SSH session has the login
keychain locked and cannot show the key-access prompt. A reboot clears any
earlier unlock. It is not the jar: the owner runs security unlock-keychain
~/Library/Keychains/login.keychain-db in that session, then make sign. If the
bundle stamp notarize.sh prints differs from the checkout's commit, rebuild (#687).
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

