A gate is a pause button. When a worker tries to do something that looks risky, Nyx stops and waits for you to say "yes" or "no" before continuing. Here's exactly how that works and what goes wrong when nobody decides.
A gate is a traffic light that turns red when a worker tries a command that could touch something sensitive, like a secrets file or credentials. The worker stops at the light and waits. You decide: green (approve) or red (reject). If nobody shows up to flip the light, it auto-turns red after 10 minutes and the worker has to try a different route.
The moderator watches every shell command a worker tries to run. If the command matches a pattern that looks like it might touch sensitive files, the moderator intercepts it before it executes and creates a gate.
The current patterns that trigger a gate are:
cat .env.example gets gated even though .env.example is meant to be committed and contains no real secrets.
The rule doesn't yet distinguish read vs write, or .env.example vs .env.
Smarter rules are on the roadmap.
Example: cat api/.env.example to see what env vars the app expects.
The command never runs. Instead, a row is inserted into the push_gate_queue database table
with status pending. The worker's HTTP request hangs open, waiting for an answer.
The gate sits at pending until someone approves or rejects it via the TUI or API. Right now there's no banner or alarm in the TUI — gates just silently pile up.
After 10 minutes with no answer, the gate auto-rejects. The worker gets a "permission denied" result back from the tool call. Status becomes timed out.
The worker sees "permission denied," reasons that it should try a slightly different approach to the same task, and runs another command that matches the same pattern. A new gate is created. Another 10-minute hang begins. Spend climbs because every retry costs an API call.
If you approve: the command runs, the worker gets the result and continues normally.
If you reject: the worker gets a clean "permission denied" and knows not to retry that exact path.
Status becomes approved or rejected.
Between 2026-05-28 22:20 and 2026-05-30, 35 gates accumulated with status pending and zero resolved. Nobody knew to look at them because there was no visible signal in the TUI or terminal.
27 of the 35 were workers trying to read .env.example files — completely harmless read-only operations
that happened to match the gating regex. Each one timed out after 10 minutes, the worker retried,
and the cycle continued. During every 10-minute hang, no other work could proceed in that worker slot.
Switching to a more capable model (Opus vs Sonnet) makes no difference here. Every model hits the same floor.ts regex and the same 10-minute waiter. A smarter model might find a non-gated workaround faster, but it can't skip the gate. Opus costs 5x more per call, which makes the retry loop significantly more expensive.
Gates live in the push_gate_queue table. Right now, the fastest way to drain them
is via the moderator API directly. A TUI gate column is being built so you can do this without leaving the terminal.
Approve-all unblocks every waiting worker immediately. Best when you trust the batch and want to resume work fast.
Reject-all kills the stalled workers cleanly. Best when you want a fresh start — the concierge can then re-dispatch with better instructions that don't hit the gate.
Approve when the command is read-only and the file doesn't contain real secrets.
cat .env.example, git check-ignore api/.env, ls config/
are all safe to approve.
Reject when the command is trying to write to, copy, or expose a real secrets file.
Anything touching .env (not .env.example), private keys, or API credentials in a non-template file.
The fix for "35 gates with no signal" is adding a dedicated column to the TUI that shows pending gates in real time. When gates pile up, a banner will flash across the top of the screen so it's impossible to miss.
The column will show: which worker triggered the gate, what command it tried to run, and buttons to approve or reject inline without switching to a separate terminal.
The regex will be updated to whitelist .env.example files (which contain no real secrets)
and to distinguish read commands (cat, head, grep) from write commands
(cp, mv, echo >, sed -i).
Most of the 35-gate incident would have been avoided with this change alone.
Waiting for your decision. Worker is blocked.
You said yes. Command ran. Worker continued.
You said no. Worker got a clean denial.
Nobody decided. Auto-rejected after 10 min.