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.

First-pass checks

Start with the shortest fixes before you dive into the longer runbook.

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.