When the first run is not boring yet
Use the shortest diagnostic path before opening an issue.
This page is meant for the first frustrating ten minutes: permissions did not appear,
launchd is not loaded, or the Web console is not behaving the way you expected.
Snapshots landed in the wrong place
Check config/notes_snapshot.env first. If you never changed it, the default
export destination is $HOME/iCloudDrive/NotesSnapshots.
# review the runtime config
config/notes_snapshot.env
Permission prompt never appeared
Run one manual snapshot first. The first `run --no-status` is the moment macOS can ask for Apple Notes and AppleScript access.
./notesctl run --no-status
Scheduler looks missing
Reinstall the launchd job and verify the current checkout path is the one you expect.
./notesctl install --minutes 30 --load
./notesctl doctor
Health looks stale
Check whether the last success is old or missing, then inspect logs and doctor output before assuming the exporter broke.
./notesctl status --full
./notesctl verify
./notesctl log-health --tail 200
Web console access problems
- Keep it on loopback by default
- Set `NOTES_SNAPSHOT_WEB_TOKEN` before enabling it
- Use the doctor and access-policy surfaces before widening the bind address
- Read the Local Web API guide if your same-machine workflow also depends on the JSON endpoints
`launchd` is loaded, but there is still no successful snapshot
Treat this as a three-layer mismatch instead of a single mystery status. The config layer
can be valid, `launchd` can be loaded, and the local state ledger can still be empty if
the first manual export never completed successfully.
./notesctl status --full
./notesctl run --no-status
./notesctl verify
If verify still says no last_success record, the scheduler is
not the only thing to inspect yet. Finish one manual export first so the ledger has a
successful baseline.
You moved the checkout after installing the scheduler
Reinstall the launchd job when the checkout path changes. The path-aware runtime mirror
and scheduler bridge must be pointed at the current checkout, not the old location.
./notesctl install --minutes 30 --load
./notesctl rebuild-dev-env
./notesctl doctor
Use the rebuild step when you also rely on the repo-owned verification environment or the
optional local Web console.
Web token or scope settings do not match the action you need
The local Web console is intentionally strict. If the console looks online but actions are
missing or blocked, inspect the token and allowlist contract before you assume the server
is broken.
./notesctl doctor --json
# review these settings in config/notes_snapshot.env
# NOTES_SNAPSHOT_WEB_TOKEN
# NOTES_SNAPSHOT_WEB_TOKEN_SCOPES
# NOTES_SNAPSHOT_WEB_ACTIONS_ALLOW
# NOTES_SNAPSHOT_WEB_READONLY
Keep the Web surface on loopback unless you have a clear reason to widen it. A narrower
token and action allowlist is the safer default.
If you want the browser/API contract spelled out before changing config, open the
Local Web API guide or run ./notesctl audit --json.
AI Diagnose is only showing the deterministic fallback
That is the expected baseline when no AI provider is configured. The command still works
as a deterministic diagnosis layer, but AI-assisted inference only appears after you opt
into a supported provider.
./notesctl ai-diagnose
# optional Switchyard-backed provider settings in config/notes_snapshot.env
# NOTES_SNAPSHOT_AI_ENABLE=1
# NOTES_SNAPSHOT_AI_PROVIDER=gemini
# NOTES_SNAPSHOT_AI_MODEL=gemini-2.5-flash
# NOTES_SNAPSHOT_AI_BASE_URL=http://127.0.0.1:4010
AI Diagnose remains advisory even when a provider is configured. The canonical truth still
comes from status, doctor, verify, and
log-health.
You just ran clean-cache, and now status looks empty again
That can be a normal cleaned-checkout baseline. clean-cache removes
repo-local rebuildables and disposable-generated support files under
.runtime-cache/, so the local state ledger may look empty again until you
rebuild the maintainer environment and record another successful snapshot.
./notesctl rebuild-dev-env
./notesctl status --full
./notesctl run --no-status
./notesctl verify
./notesctl doctor
This cleanup lane does not delete exported snapshots under
NOTES_SNAPSHOT_ROOT_DIR. It only resets repo-local support surfaces.
Runtime residue is living outside the repo root
Treat repo-local cleanup and machine-level residue as two different closets. Repo-local
rebuildables live under .runtime-cache/. Repo-owned machine-level residue
lives under the current repo-managed machine cache root.
./notesctl runtime-audit
./notesctl clean-runtime --dry-run
./notesctl clean-runtime
runtime-audit is the safe first look. It reports both current roots and
legacy migration roots so you can see whether stale launchd/runtime/browser/vendor
residue is still hanging around from an older path contract.
The persistent isolated browser root is reported, but it is not part of TTL/cap cleanup.
Only disposable browser temp state under browser/tmp/ is cleanup-eligible.
Browser automation cannot find or attach to the isolated Chrome instance
The browser contract here is strict on purpose. This repo no longer shares the default
Chrome root. It keeps its own isolated browser root, with one on-disk profile dir fixed
to Profile 1 and the display name still set to
apple-notes-snapshot.
./notesctl browser-bootstrap
./notesctl browser-open
./notesctl browser-contract
# config/notes_snapshot.env
# NOTES_SNAPSHOT_BROWSER_PROVIDER=chrome
# NOTES_SNAPSHOT_BROWSER_ROOT=<repo-owned-browser-root>
# NOTES_SNAPSHOT_CHROME_USER_DATA_DIR=<repo-owned-browser-root>/chrome-user-data
# NOTES_SNAPSHOT_CHROME_PROFILE_NAME=apple-notes-snapshot
browser-bootstrap is the one-time copy step from the default Chrome root.
browser-open is the single-instance launcher/attacher. After that,
automation should attach over CDP instead of second-launching the same root. If the env
is missing, the profile is absent, or another Chrome instance already owns the configured
CDP port, the contract fails fast instead of drifting into the wrong browser session.
Do not treat browser-bootstrap as a routine sync command. Once you add or
refresh logins inside the isolated repo-owned root, rerunning bootstrap would replace that
isolated root from the default Chrome root again.
The default CDP port is 9337. If your machine already uses that port for a
different local tool, choose a deliberate override before launching this repo-owned
instance, for example:
NOTES_SNAPSHOT_CHROME_CDP_PORT=9347 ./notesctl browser-open
NOTES_SNAPSHOT_CHROME_CDP_PORT=9347 ./notesctl browser-contract --json
You are trying to compare local verification with CI
Local maintainer verification and GitHub CI are related, but they are not the same lane.
Local verification helps you reproduce the repo-owned gates on your Mac. GitHub CI for
this repository runs on GitHub-hosted runners, not on a local self-hosted runner.
./notesctl rebuild-dev-env
./.runtime-cache/dev/venv/bin/python -m pre_commit run --all-files
PYTHON_BIN=./.runtime-cache/dev/venv/bin/python scripts/checks/ci_gate.sh
If local verification is green and a pull request still fails, treat that as a repo or
CI-environment mismatch to inspect, not as proof that this repo expects you to run CI on
your own machine.
Still blocked?
If this page did not get you unstuck, use the public
Q&A discussion.
Open a bug issue only when you can provide a reproducible repository or runtime failure.
If you want an advisory explanation before opening anything, try
./notesctl ai-diagnose and then compare it with the deterministic output from
status, doctor, and log-health.
If you are debugging the workflow from an MCP host, use ./notesctl mcp so
the host reads the same local diagnostics contract instead of screen-scraping the Web
console.