scheduled-jobs
Skill2 starsChanged 2 months agoMITView on GitHub
Why each launchd LaunchAgent on this machine exists, scheduled at the hour it is, and which plausible "fixes" are wrong. Load before creating, editing, moving or diagnosing any scheduled job. Current state comes from jobs-inventory.sh, never from this file.
- Reads credentials
- Commits and pushes
6 other projects use a version of this.
Adopted by
Most recently taken up by
- fmind/dottheir own version, first seen Nov 8, 2026
- fmind/dotfilestheir own version, first seen Nov 8, 2026
- Happy-Technologies-LLC/happy-platform-skillstheir own version, first seen Nov 1, 2026
- KMaruthi2002/universal-llm-skillstheir own version, first seen Oct 25, 2026
- orangehrm/orangehrmtheir own version, first seen Oct 8, 2026
- aborroy/aiup-alfrescotheir own version, first seen Jun 22, 2026
Other versions6 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 6
- orangehrm/orangehrm★ 1.1ksame name
- Happy-Technologies-LLC/happy-platform-skills★ 41same name
- aborroy/aiup-alfresco★ 14same name
- fmind/dot★ 13same name
- fmind/dotfiles★ 13same name
- KMaruthi2002/universal-llm-skills★ 1same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository11 other files it gives its agents.
- .claude/CLAUDE.mdCLAUDE.md
- CLAUDE.mdCLAUDE.md
- ci-and-branch-protectionSkill
- claude-md-hygieneSkill
- design-critiqueSkill
- design-directionSkill
- design-tellsSkill
- memory-and-plansSkill
- portfolio-conventionsSkill
- python-uvSkill
- testing-conventionsSkill
The file
We split this skill into the rules it gives an agent
2 rules. 0 are common, 1 is distinctive, and 1 is very specific to this project.
0
Common
Most of the 6 edited versions we read keep these.
1
Distinctive
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 9
Current state is derived, never written here: Run ~/.claude/scripts/jobs-inventory.sh (add --json) for the live list: label, schedule, …
- Distinctive: line 286
Do not "tighten" the guard back.
“Common” is measured: kept by at least 6 in 10 of the 6 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Scheduled jobs — the reasons
Current state is derived, never written here. Run
~/.claude/scripts/jobs-inventory.sh (add --json) for the live list: label,
schedule, program, log, log age, last exit. This file holds only what cannot be
derived — the judgment behind each job. Never add a count, a roster, or a file
list to this file. Every count previously kept in prose here drifted.
Exit convention, portfolio-wide: 0 = healthy · 1 = a finding · 2 = could not run = unknown, never "healthy" and never "nothing to do". A mounted-but-empty volume, an empty catalogue, an unauthenticated API returning zero rows — all are unknown.
Cross-cutting rules
Never schedule anything that reads the login keychain from crontab. Four
instances, each found the hard way: claude's OAuth credentials, gh's OAuth token
(~/.config/gh/hosts.yml carries no oauth_token: line), an IMAP password, and the
Elenchus status secret. A crontab job runs outside the GUI login
session, so login.keychain-db is absent from its search list. The failures are
silent, not loud: claude reports Not logged in; gh returns HTTP 401; the
security lookup returns an empty string with no error, so the job reports
"nothing new" forever. Remedy is a LaunchAgent in ~/Library/LaunchAgents/, which
runs inside the GUI session. Do not "fix" it by adding USER= — real cron
already sets it; that is a red herring that mimics the PATH bug below.
Every plist carries its own EnvironmentVariables.PATH. launchctl getenv PATH
returns nothing here, so a plist without it inherits launchd's built-in
/usr/bin:/bin:/usr/sbin:/sbin. Real dependencies: node/npm from fnm's
~/.local/share/fnm/aliases/default/bin — always the aliases/default symlink,
never a node-versions/<v> path, which one fnm install re-arms; claude from
~/.local/bin; gh and uv from /opt/homebrew/bin. jq and curl are Apple's
in /usr/bin and need nothing. fnm's node is v24, Homebrew's is v26, and Vercel
caps at 24 — falling through to the Homebrew binary builds on a version the deploy
platform refuses. Check which -a before asserting a binary needs Homebrew.
Nothing scheduled here calls a bare python3. A standalone script carries a PEP
723 header and a #!/usr/bin/env -S uv run --script shebang, and the plist executes
the file directly so that shebang selects the interpreter. Passing the script as
an argument to an interpreter bypasses the one place its dependencies are declared.
A binary guard does not cover a project's installed dependencies. Guards check
node, not node_modules. A 2026-08 disk sweep deleted 16 repos' node_modules;
every guarded binary was still present, so vigie-refresh ran, regenerated
snapshot.json, then died in the build step that then existed. The visible result
was the worst shape available: a fresh snapshot behind a frozen page, answering
with numbers that looked current. The build is gone, but the lesson is not
vigie-specific. Before deleting a dependency directory, list which
scheduled jobs build from that repo.
When a script's header states a cadence, check that something fires it before
believing the cadence. Three jobs have been found describing themselves as
scheduled while nothing triggered them, distribution-watch among them. A script that
has never run leaves nothing behind to notice, and a status file records only runs
that happened.
When you move a scheduled job, the trigger is the deliverable, not the prompt. A
status.json records runs that happened, never runs that were never triggered — so a
missing trigger is indistinguishable from a quiet week. After any move, fire the job
once and read its log before calling the migration done.
Wrong beliefs a future session will re-derive on its own
An assumed quota-reset date is not a measured one. The 2026-08-18 session recorded 2026-09-17 as the reset; the real reset was 2026-09-01, found only by watching a previously quota-blocked run turn green. Also: a public repo is off the included-minutes meter entirely, so a repo flipping public silently removes it from any burn projection.
Do not grant Full Disk Access to uv. TCC's kTCCServiceSystemPolicyAllFiles
table contains no row for uv at all — the grant has never existed — and
devlog-collect runs to exit 0 in ~10 s writing onto the Drive. rclone
(the Drive mirror job) reads the same mount with no TCC row either. A hang on a Drive
path is the FileProvider not yet materialising, not a missing grant. Note claude
carries an explicit FDA denial (auth_value 0) and still reaches the Drive mount.
A bash script launchd starts itself cannot list iCloud Drive; start it through
icloud-host.py. Measured 2026-11-07 with one-shot LaunchAgents: ls and
rsync on an iCloud directory get "Operation not permitted" when /bin/bash
is the process launchd started, and succeed when uv's Python is (the host runs
the script as its child). Writing to a known path still works from bash —
db-backup's mv lands every night — so a job that only writes can look fine
while one that lists fails. Do not "fix" it by granting Full Disk Access to
/bin/bash: that hands every bash job full disk access.
Per-job judgment
tech-debt-triage — Phase 2 deep-review selections stay manual. Items flagged
2+ months without action escalate L1→L2→L3 to troubleshooter.
cleanup — runs cleanup-cron.sh, not claude -p "/cleanup". /cleanup
commits at Step 1, can fire /sync-setup at Step 4, and its CONFIRM tier is
interactive by design — none of that is safe unattended. The wrapper covers Step 0's
AUTO tier only and logs the rest as owner action:.
usage-watch — guards on curl and a uv-managed Python with a fatal exit
rather than a skip, so a missing interpreter cannot be mistaken for a quiet week.
Its findings, payload growth included, notify through notifier.sh itself:
vigie drops exit 1 on the assumption that the job already
reported it. A run that measured nothing (no route list, zero routes, every probe
failing at the transport level, the ratchet crashing) is exit 2, never a finding.
Hosts that answer non-2xx or catch-all are findings, even when every one does.
So is a Vercel token the API rejects (401/403, or a JSON error body): a dead
discovery must not read as a quiet week.
The baselines follow the rules written at the top of the script's ratchet. A
route still listed but not measured keeps its entry, because dropping it would
reset that route's growth ratchet. Only a carried entry written before
measured_at existed gets measured_at: null, never an invented date; a
carried entry that already has a date keeps it. An ignored host's entry is
dropped, because nothing will ever refresh it. A route is dropped only when it
verifiably left the overlay, or when discovery answered without it: a failed
discovery, or a run where every probe failed at the transport level, prunes
nothing discovery could have produced.
model-watch — a model chain hides its own degradation, and a delisted entry
is worse than a degraded one: OpenRouter validates the whole models array up front,
so one stale entry 400s a request the primary could have served. Refuses to read an
empty catalogue as everything having been delisted.
Two discovery sources, because one missed an outage: wrangler.toml chains, and
url:/model: literal pairs in Netlify functions — my-bias-app's Groq model sat in a
.ts constant, was retired on 2026-08-16, and 502'd for two weeks unseen. Groq has
no public free catalogue, so its ids are checked against the public deprecations
page, reading only the first <code> of each table row — the third column is
the replacement, and a whole-page substring grep flagged the replacement as
retired on its first run. Its falsifying control is that the retired set still
contains llama-3.3-70b-versatile; otherwise exit 2. Here-strings, not pipes,
for the membership tests: under pipefail, grep -q closing the pipe on its
first match gives printf a SIGPIPE on a large string, and that "failed" pipeline
reads as not found — a false negative, the silent kind.
Listed is not served. On 2026-10-22 a chain entry stayed listed while its only provider sat at 0% uptime for a day, every Elenchus call ended on its 404, and this job said "ok". Each OpenRouter entry is now also read from /models/<id>/endpoints: no provider up is DEAD (a finding), an unreadable answer is UNKNOWN (exit 2). An id without :free is a deliberate paid fallback and only has to be listed. It still cannot see a saturated free pool (429s): only the proxy's [upstream] log can.
devlog-collect — the plist executes collect.py directly so its uv run
--script shebang selects the interpreter. See the FDA note above.
graphe — daily build of the writings graph and the corpus index
(my-editor-app/tools/graphe, my-editor-app/tools/index), via graphe.sh. Daily since
2026-11-01 so a text reaching final, a newer .pages or a published post reaches the
index the next morning; the index costs no model call, and the graph re-extracts only
changed texts. 08:47, not 08:37: 08:37 is held every day by another agent, and 08:47
is clear of the other jobs (checked against jobs-inventory.sh on 2026-10-27, and for
every weekday on 2026-11-01). The indexer needs Node 24 by path and two installed
dependency trees — my-editor-app/node_modules (the stemmer) and
my-editor-app/tools/index/node_modules (the model runtime); the script checks both and
exits 2 without them, so list this job before deleting either. LaunchAgent because the tool calls
claude -p (OAuth in the login keychain) and reads the Google Drive File Provider
mount, which exists only in the GUI session. The per-run extraction cap lives in
the script, not the plist: an unattended run must be cost-bounded, and a backlog
shows up as differes in the journal and drains over the following runs. Notifies through
notifier.sh only on exit 1 or 2, with journal counts as the body, never a path
or a citation (the topic is public). Every run, success included, ends with one
timestamped summary line so the log's age stays a liveness signal. The script owns
the number and the paths.
portrait — weekly consolidation of the owner portrait (~/.claude/portrait.md)
from the week's auto-memories, via portrait-consolide.sh. It proposes and never
writes the portrait: the portrait is imported into every session, so one wrong
fact misleads all of them at once, and an unattended model must not be the one to
put it there. It leaves a proposition file; the owner applies or dismisses it from
/portrait, which runs portrait-check.sh after the write and restores the old text
when the check refuses. Sunday, after graphe: the week's memories are closed,
and the two model calls do not start in the same minute. LaunchAgent for the same
keychain reason as every claude -p job. The window, the pinned model and the call
timeout live in the script; nothing calls the model when no memory is recent.
Notifies through notifier.sh only on 1 or 2, counts only, never a proposed text.
setup-watch — weekly health sheet for the Claude Code setup itself, via
setup-watch.sh; it proposes and applies nothing. It watches three things, each
because it fails without a sound: the login expiry (an expired OAuth login
turns every claude -p job into Not logged in at once), the unreviewed
CHANGELOG versions (upstream changes land in settings.json, hooks and
frontmatter semantics without anyone being told), and the scheduled jobs
failing. LaunchAgent for the keychain reason, as every claude caller. No model
in v1: it is a delta and a list, and the model stage is revisited only after a
few sheets have been treated by hand, together with a generic propositions tab —
deferred because the verbs do not share a meaning (a portrait edit is mechanical,
a setup item is session work) and the sheet format is known to be provisional.
The CHANGELOG, not the RSS: the RSS drops every Fixed line, and those are
the ones that explain a behaviour that changed under a hook. Versions are skipped
upstream, so membership is by exact heading line, never by number, and the
marker's own heading is a falsifying control: absent from the fetched body, the
section is unknown (exit 2), never "everything is new". The marker advances only
through --traite, run by the owner from /setup-watch: a marker that advances
by itself either marks unread versions as reviewed or grows without bound.
News alone is exit 0: a weekly exit 1 on routine releases teaches the reader
to skip it, and vigie drops exit 1. Failures are keyed on first sighting in the
job's own state, not parsed from heterogeneous logs, so the age is "at least",
never the failure's real start. An unknown launchd exit (-, (never exited))
after a reboot or reload keeps the first-seen date; dropping it would reset
the age at every reboot and hide a long failure. Two exemptions on exit 1, for
different reasons: portrait (1 means a proposal was written, not a failure) and
setup-watch itself (its 1 restates findings already listed, so counting it makes
every finding re-report itself the following week). The marker lives in state/,
which is not backed up: losing it gives exit 2 repère absent until re-seeded,
the honest failure. The script owns the threshold, the timeouts and the item cap.
vigie-refresh — its script lives in a project repo, like devlog-collect's
and the tool graphe.sh launches, so /sync-setup covers none of them and each
runs whatever branch that checkout has. It collects the snapshot and then pushes
what changed: since 2026-09-13 vigie has no panel, so this agent is the whole
of it. Guards on node/npm/git only, fatal exit 2: without those no
snapshot exists at all.
gh and uv are deliberately NOT fatal. Each feeds exactly one collector, and
runCollectors isolates a collector that throws — a missing gh records
{ok:false}, the columns render SIGNAL LOST, and the band names the missing
source. That is the designed, visible, one-column failure. Making them fatal was
tried and is strictly worse: the script exits before the collect, so nothing
regenerates the snapshot and every push is computed from a stale one — every
source ok:true and nothing wrong, under a generated_at nobody reads.
A guard written to prevent a plausible-looking answer nobody measured was
manufacturing one. It cds into the repo first, deliberately: the collectors
resolve .env, the snapshot and the previous snapshot against the cwd, and a
scheduled job runs with cwd $HOME.
It calls notify.mjs and must read three exit states. Under
set -euo pipefail a bare call would mark this agent failed in exactly the
months an alert fires, because 1 means a finding, not a failure. 0 and 1 are
both success; only 2 — could not run — stops the script. VIGIE_DIGEST_DOW
gates the Monday audience digest on the same agent rather than a second plist.
gate-watch — here a cron failure would be worse than a visible error:
unauthenticated gh search prs returns zero rows, byte-identical to "no repo has any
PRs", so it would report full coverage forever. It checks gh auth status explicitly
rather than inferring from an empty result set. Monthly because a dormant repo
starting to take PRs is a slow signal.
db-backup — pg_dump of every database in ~/.claude/db-backup.json, to iCloud outside dev-mirror. LaunchAgent because the connection strings are in the login keychain. The plist PATH carries /opt/homebrew/opt/libpq/bin because libpq is keg-only. It needs the DIRECT Neon host: a pooled (-pooler) string is rewritten, but store the direct one. A missing keychain item is exit 2 (unknown), and an empty or tiny dump is exit 1, never a success. Retention and the size floor live in the script.
umami-retention — deletes Umami visit data older than 13 months (owner decision 2026-10-19), monthly on day 2. LaunchAgent because the DIRECT Neon URL (item umami-retention) is in the login keychain; plist PATH carries libpq like db-backup. One all-or-nothing transaction; sessions are removed only when no event, session_data or revenue row still references them. The script owns the number and the table list. A connected-but-not-Umami database is exit 2, never "nothing to delete". Take a Neon snapshot before the first real deletion.
elenchus-watch — the hour is the design, not a free slot. The proxy's
counters roll over at 00:00 UTC, computed inside the Durable Object from its own
clock. 23:47 Paris is 21:47 UTC — ~91% of the UTC day elapsed in summer. A morning
run would sample a UTC day a few hours old and read near-zero every single day,
reporting "plenty left" on the very day the service refused everyone at 23:00. Do
not move it to a breakfast slot to sit beside the others. The route it reads is
deliberately invisible rather than closed: an unauthorized caller gets the same
405 Method not allowed as any other GET, byte-identical, because a 404 or 401
would announce it exists. Its secret is not the extension's X-Elenchus-Key,
which ships inside the .crx. A 405 is exit 2, not exit 0. Status reads claim no
quota — a test pins that six status reads around one analysis move the counter by
exactly one, so watching the service cannot consume what it watches.
netlify-plan-watch — the Netlify account is on the Legacy Free plan, which
has no credits. On a credit plan each production deploy costs credits, and running
out pauses every site on the account, the hub included. So the event worth
catching is Netlify ending Legacy, and the job watches two sentences on the "How
credits work" docs page: the Legacy one and the deploy-cost one. No deploy
counter, deliberately: on Legacy a deploy costs nothing, so counting deploys
measures nothing. A finding never rewrites the baseline. It re-alerts every
week until the owner reads the page and runs the script with --accept; an alert
that clears itself after one push is lost in a busy week. A missing page anchor is
exit 2: the page moved, and nothing was measured. It reads no keychain, and it is
a LaunchAgent only so that jobs-inventory.sh sees it.
distribution-watch — are the publish-once channels actually done: npm, PyPI,
awesome-list PRs, the Chrome Web Store listing. Reports state, never proposes
content, and checks for an already-open PR before naming "open a PR" as an action.
The 3rd and 17th rather than the 1st and 15th, which are taken at 08:07.
dev-snapshot (~/Library/LaunchAgents/com.example.dev-snapshot.plist,
03:17) — one-way mirror of ~/Dev onto iCloud Drive, rsync -a --delete. A git
remote covers tracked files only; this covers gitignored personal files, repos
never pushed anywhere, and model weights that live in a project but never went
to GitHub. Mirror, not archive: a local deletion propagates within the next
run (worst case ~24 h), and the only undo is iCloud's own 30-day Recently
Deleted — this script has no undo of its own. 03:17 was picked clear of every
other agent, the 05:xx model jobs in particular; jobs-inventory.sh gives the
occupied slots before any move.
Two guards stand between a partially-wiped ~/Dev and a wiped mirror. A
git-count floor refuses to run at all (exit 2) when ~/Dev holds fewer
*/.git dirs than a measured floor — read as UNKNOWN, never as "nothing to
mirror". And because --max-delete only caps a real rsync's deletions
(openrsync deletes up to the limit, exits 25, and would do the same again
every subsequent night — a slow-motion wipe on the same ~30-day timescale as
the iCloud undo window), the script runs a read-only pre-flight first and
refuses the entire run (exit 1, nothing touched) when the pre-flight would
delete at or above that same threshold. --max-delete stays on the real
transfer too, as a redundant second fence. Deletions under */.git/ are
not counted: a git gc repack deletes thousands of loose objects at once,
and counting them refused the mirror every night for a week in October 2026.
Do not "tighten" the guard back. A third guard refuses (exit 2) if
the source and the iCloud destination are nested inside each other. See
~/.claude/scripts/dev-snapshot.sh for the filter list and every constant's
rationale — each script owns its own numbers, not this file.
The plist starts it through icloud-host.py (the iCloud listing rule
above): launched directly, the pre-flight exited 23 every night from
2026-10-15 to 2026-11-07 while manual runs from a session succeeded.
A clean run used to be silent, which broke the rule that log age is the only
stop signal — a dead agent never exits with an error, it goes quiet, so only
the age of its log can say it has stopped. launchd does not touch a log file's mtime on a run that writes nothing, so a job
that ran fine every night for a month and one silently un-triggered for a
month (laptop asleep at 03:17, agent unloaded) looked identical — an old,
empty log and a cached last_exit 0 from months back. Fixed: every run,
success included, now ends with one timestamped summary line
(<ISO8601> dev-snapshot ok: <N> files, <D> deleted, <dur>s, exit 0, or
FAILED: exit <rc> on stderr), so jobs-inventory.sh's log-age column is a
real liveness signal for this job too, not an exception to the rule.
Every non-zero exit also notifies once through notifier.sh (job name,
exit code, counts — never a path). The liveness line cannot carry a failure
on its own: vigie drops exit 1 on the assumption that the
job already reported its finding, and the FAILED line keeps the log fresh,
so a mirror refusing every night read as healthy everywhere.
An evicted mirror copy is unlinked, not read. With the disk near full,
iCloud evicts mirror files to dataless placeholders; when the source copy then
changes, rsync mmaps the placeholder as its delta basis, a LaunchAgent may not
fault it back in, and the whole transfer dies on mmap: Resource deadlock
avoided (EDEADLK) — every night from 2026-10-16. --whole-file does not
help under openrsync. The script unlinks the dataless copies the pre-flight
says it will overwrite; any other read error still fails the run. To
reproduce outside launchd, run rsync with the process's dataless
materialisation policy set to off (setiopolicy_np) — from a terminal the
file is silently downloaded and the bug hides.
Its push has a cooldown per cause: the same failure re-pushes only after
the script's cooldown, a new cause pushes at once, and the first clean run
after a failure pushes one "recovered". A --dry-run stays outside it: it
never pushes "recovered" and never reads or writes the cooldown state. The log
keeps a line every night.
config-backup (~/Library/LaunchAgents/com.example.config-backup.plist,
03:47) — commits and pushes the two private config repos, ~/.claude and
~/.claude/plugins/local; what ~/.claude tracks is decided by its own
.gitignore allow-list, never here. LaunchAgent because git push
authenticates through the login keychain. A secret in the index is exit 2
with nothing committed — gitleaks git --staged runs before the commit and
the index is reset, so every following night stops at the same place until a
human looks; the repos' pre-commit hook is a second scan, not the gate. It
also sweeps in whatever was left uncommitted in ~/.claude that day, by
design: it is a backup, not a history. 03:47 is after dev-snapshot and well
before the 05:xx model jobs. Ends every run with one summary line, for
the same log-age reason as dev-snapshot.
midas-ohlcv-bridge (retired 2026-09-04) — the last of the quota-outage
bridges, and the only scheduled job that ever wrote to a remote. It outlived the
reset on purpose: while my-trading-app main carried a required status check, hosted
fetch-ohlcv could not push and this bridge, under the owner's credentials, was
the only OHLCV writer. It was retired only after the check was removed and a
hosted run had pushed once. Do not rebuild it — my-trading-app is public and
unmetered, so a hosted job is strictly better than one that needs this Mac awake.
Third-party agents
Non-com.example.* plists: ls ~/Library/LaunchAgents/ | grep -v '^com.example.'. Most
only watch or update themselves. Pearcleaner's homebrew-autoupdate is the
only scheduled job on this machine that changes installed software unattended —
brew update && brew upgrade --greedy && …, and --greedy upgrades even casks that
manage their own updates. Kept as-is by decision, recorded because an agent that
writes and is not written down is how a version change becomes an unexplained
breakage a week later — which is exactly how the fnm/node switch surfaced.
~/.claude/scripts/ — the conventions
Deterministic work gets a script, not an agent. The live roster comes from
ls ~/.claude/scripts/; never write it down.
Each script is the source of truth for its own numbers. disk-hygiene.sh owns
every retention number, tech-debt-triage.sh owns the four signals and their
weights, env-drift-check.py owns the two documentation tiers,
claude-md-weight.sh owns the instruction-file size threshold. The command files
describe them and must never re-implement or restate them — restated numbers drift.
Targets are DISCOVERED, never hand-listed. gate-watch.sh from one
gh search prs --owner call, usage-watch.sh from the Vercel API,
model-watch.sh from each repo's wrangler.toml, claude-md-weight.sh by walking
~/Dev. This is not decorative: a hand-picked loop over 12 repos missed my-boardgame-app
and my-bias-app, both of which had PRs.
Watchers report state; they never edit config, raise a ceiling, or open a PR.
Choosing a replacement model needs an eval set (elenchus/scripts/bake-off.mjs);
raising an Elenchus ceiling needs the provider's published RPD re-derived in the same
change. gate-watch.sh distinguishes pending (a fix already open in a PR) from
MISSING, because a watcher that nags about work in flight is one you learn to skip.
notifier.sh is the single push channel, and its topic is PUBLIC. It redacts
against secrets.json literals first and generic token patterns second, because a
failing run can quote its own curl command. That redaction covers secrets, not
personal data — never route personal content through it. Counts only.
A transport failure is retried, then spooled — never dropped. A DNS hiccup
used to lose the push outright (one attempt, exit 1). The notifier now retries
transient failures only (transport, HTTP 5xx, 429; any other 4xx is an
answer), then writes the message to
a bounded spool that the next call reaching ntfy sends first, flagged with its
queue time; overflow is counted and pushed, not discarded. Exit 1 is unchanged.
A deferred (--a) message is never spooled: its caller re-arms it itself,
so a spooled copy would arrive twice. The script owns the delays and the bound.
Anything that rotates or rewrites a job log must preserve its mtime. The log's
age is the only signal that an agent has stopped, and jobs-inventory.sh reads it
straight from the mtime — a rotation that bumps it makes every rotated job read as
having run today, the silent kind of wrong. disk-hygiene.sh's job-log tier takes a
touch -r reference before it truncates, rewrites the file in place (a running job
holds it open for append; a mv would send its output to an unlinked inode) and
restores the mtime from the reference; its test proves an unguarded truncation reads
0 days. That script owns the line counts. Logs no com.example.* plist writes are
listed and never touched — daemon.log belongs to the Claude Code daemon.
env-drift-check.py: tier 1 is .env.example/.dev.vars.example, tier 2 is
README.md/CLAUDE.md prose. Conflating them reports every prose-documented
secret as a leak-grade finding — the exact false alarm it was built to stop. It
exits 1 only on a var documented in neither.
distribution-watch.sh's Chrome Web Store check asserts the resolved slug, never
the status code — the Store answers 200 for any 32-character ID, including one that
never existed, so res.ok is unfalsifiable there. It re-proves that control at
runtime rather than trusting the measurement in its own comment.
All but distribution-watch.sh honour CLAUDE_DIR / DEV_DIR / LAUNCH_AGENTS_DIR
so they can be tested against a fixture tree. That one reads no local tree — it
queries public registries, so there is nothing to redirect.
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.

