NAI OS
One script, two triggers, one decision: notify or stay quiet. The only path into nai-analysis that never writes anything back to it.
Two things can start this: you typing /nai-issue-check, or an hourly cron
firing on its own (7 * * * * — seven minutes past the hour, deliberately off
the top-of-hour spike). Both run the exact same steps, because both are just the command calling
scripts/issue-bridge/check-activity.mjs with the same cursor file. There is no
orchestrator wave here and nothing to dispatch — the whole system is one deterministic
script plus one decision about whether to notify.
The script queries nai-analysis’s
Supabase directly — the same service-role credential
/nai-issue-to-agent already holds — for anything new
since a local, gitignored cursor (.notify-cursor.json) was last advanced: comments
on issue_comments, decisions on issues, and, if
OWNER_USER_ID is configured, brand-new issues created by anyone other than Udo. The
cursor is captured as “now” before querying and only advanced after every
query succeeds, so a mid-run write is reported next time rather than silently skipped. A missing
cursor defaults to “now,” not “the beginning of time” — a first run
never dumps all history as if it just happened.
%%{init: {'theme':'neutral', 'flowchart':{'htmlLabels':false,'nodeSpacing':45,'rankSpacing':55}}}%%
flowchart TD
H1["You type<br/>/nai-issue-check"] --> C
H2["Hourly cron<br/>7 * * * * (this session)"] --> C
subgraph L1 ["check-activity.mjs — no orchestrator wave, just a script"]
C["Read cursor → query → advance cursor<br/>prints OK or ERROR, always"]
end
C -->|"select"| DB[("nai-analysis Supabase<br/>issue_comments · issues · profiles")]
DB -.-> C
C --> R{"OK {findings, warnings}<br/>or ERROR"}
R -->|"findings non-empty"| N["PushNotification<br/>one line, everything this run"]
R -->|"findings empty"| Q["Nothing new — no notification"]
R -->|"ERROR"| E["Relay the error —<br/>never treated as quiet"]
classDef orch fill:#dcefe9,stroke:#0f6f63,stroke-width:2px
classDef tool fill:#f4f1ea,stroke:#8a7a5a
classDef human fill:#fdf3e0,stroke:#8a5a00,stroke-width:2px
class C orch
class DB tool
class H1,H2,N,Q,E human
PushNotification — a plain Node
process has no access to Claude Code's tool layer. It only prints a status line; the calling
layer (the command, or the scheduled routine running the same steps) reads it and decides.
One notification per run summarizing everything found, never one per finding, never on an
empty result, and never on ERROR — an unknown outcome is not the same as a
quiet one.The boundary here is the plainest in the repo: this path never writes to nai-analysis at
all. Compare /nai-issue-to-agent, which holds the same
credential but uses it for a single scoped insert after a human confirms —
this script only ever selects. The one thing it does write is local: its own cursor
file, and the notification itself, which is a Claude Code push to Udo, not a row in anyone's
database.
The two triggers exist for a specific, deliberate reason and aren't interchangeable
conveniences. The hourly cron runs via CronCreate inside this local session
rather than as a cloud routine, because a cloud routine runs in an isolated environment with no
access to local files or environment variables — it could not read
scripts/issue-bridge/.env. Embedding the Supabase service-role key directly in a
cloud routine's stored config instead was considered and rejected: that key bypasses row-level
security entirely, and putting it in Anthropic's routine storage is a materially broader exposure
than the project's existing local-only, gitignored .env. The accepted trade-off:
the hourly check only fires while a session stays open, and the cron auto-expires after 7 days
and needs periodic re-arming — /nai-issue-check is what covers the gaps
in between.