Omniscio documentation
Browse all documentation
  1. Getting Started13
  2. Sessions & Agents115
  3. Inbox & Notifications59
  4. Projects & Tasks95
  5. Automation & Scheduling75
  6. Knowledge & Memory26
  7. AI Features60
  8. Integrations100
  9. Plugins & Marketplace33
  10. Cloud & Teams56
  11. Settings & Customization58
  12. Account & Billing28
  13. Troubleshooting84
  14. CLI & API Reference22
  15. Legal & Policies4
  16. Uncategorised22

Git guardrails

The rules that stop a session doing something destructive to your git history — committing to a protected branch, force-pushing, or throwing away work — with a warn-only mode, per-action approval, and escape hatches for when you do mean it.

What it is

Status: shipped, ON by default. Every user's protected branches (master/main) are guarded out of the box. Configure it, switch it to warn-only, or turn it off in Settings → Features → Git guardrails or the Dev Pipeline panel → Setup tab → Git guardrails (the same controls, in two discoverable places).

It lives in the Dev Pipeline panel because that panel and these guardrails both exist to protect master/main while agents work — but note the rail protects every spawned agent session, not just Dev Pipeline runs.

Git guardrails is an Omniscio safety feature that stops the AI sessions Omniscio spawns from doing dangerous git operations on your behalf — most importantly committing or pushing directly to a protected branch (master / main). When it's on, Omniscio quietly installs a small safety check into every session it launches, so if an agent tries a gated git command the command is blocked before it runs and the agent is told why.

It's the built-in, one-switch version of the hand-installed PowerShell "block-master" hooks some power users run today: instead of each person wiring up their own machine, Omniscio bundles the check and turns it on for the whole session automatically.

It is a safety rail, not a security sandbox. It catches the common, honest cases — an agent that reaches for git commit while sitting on master, or git push origin master. A determined agent that deliberately obfuscates a command (hiding it inside $(...), eval, an alias, or a script it writes and runs) can get around a shell-command checker. Treat it as guardrails on a mountain road, not a locked vault.

Where to find it

Pausing it

Three temporary off-switches. Two are yours alone; the third lets a blocked agent unblock itself for a few minutes, and is deliberately narrower than the other two.

  • For one agent — pause a single session for a set number of minutes. Every other session stays guarded, so one stuck agent no longer costs the whole fleet its protection. Turn on Until I turn it off instead of choosing minutes and that session stays unblocked — its row reads Always — until you press Resume now. The app quietly renews it in the background (each renewal still has the normal 4-hour limit, so a faked file can never grant forever), and it carries over when you restart the app. Only you can turn this on, from Settings; no agent can.
  • For the whole machine — the original pause window, when you want a free hand everywhere.
  • The agent unblocks itself — capped at 10 minutes, 3 times an hour, and it always posts a note to your inbox saying which session did it and why. It never allows pushing to a remote, and never a destructive git stash — both of those can damage something that isn't the agent's own work, so they still take your approval. Turn the whole thing off with Let an agent unblock itself briefly (Settings → Features → Git guardrails) and you are back to approving every time.

No pause of any kind lifts #8–#10. "Let me write to master for a while" should not also mean "let me mass-close pull requests or kill the app" — those have their own single-command overrides.

When you get asked to approve

If an agent needs something it can't grant itself — a push, or a longer window — it raises a card in your inbox naming the command it was blocked on. The card has an Approve button: one click, one confirmation, and that one session is unblocked for 20 minutes. Everything else stays guarded.

The same card also appears in the conversation, right above the message box of the session that asked, with the same Approve / Decline buttons and the agent's own stated reason. So you can answer without leaving the thread you were reading — which matters most on your phone, where the inbox and a session are two different pages. It is one approval shown twice: answer it on either screen and it disappears from both, and the waiting agent is told either way.

This covers both ways an agent can end up waiting on the guardrails: asking for a short window on itself, and asking to change a guardrails setting. Approvals about anything else stay in the inbox only — the message box is not a second inbox.

An agent can only ever ask on its own behalf. The session that gets unblocked is the one the app recorded as raising the card, not a name the agent typed — so a card can never trick you into unblocking a different agent.

Always allowed: reads (git status / log / diff / show / …), moving onto a protected branch (git switch main), creating a new branch, and git fetch.

Worktree commands: all of them go through the app (changed 2026-09-01)

While the Dev Pipeline is on for a repo, an agent working in it cannot run any git worktree command that changes something — add, remove, move, prune, lock, unlock or repair. Looking is untouched: git worktree list and a dry-run prune work exactly as before.

Two of those refusals fire in EVERY guarded session, Dev Pipeline or not. raw-worktree-add (a bare git worktree add) and forced-worktree-remove (git worktree remove --force) are deliberately branch-independent standalone rules — they read nothing from the Dev Pipeline's worktree-lockdown switch, so turning the pipeline off does not make them go away. They do have a sanctioned way through, which the wider lockdown does not: AMC_ALLOW_RAW_WORKTREE_ADD=1, for a repo the create API genuinely cannot serve (it 404s, or the server is down). The refusal message names it. It is a fallback for an unserved repo, never a way to skip the ledger when the route would have worked.

The reason is ownership. A worktree made or destroyed by hand is invisible to the app until a background sweep notices it, and that sweep can never recover the two things only the person who made it knew: who is working in it, and why. Measured on the operator box the day the first version of this rule landed — of 755 recorded worktrees only 108 came from the app's own call, 647 were adopted after the fact, and 57 of the 118 open ones had no owner at all.

Each refusal names the replacement for the exact command that was tried:

Instead of The agent uses
git worktree add POST /worktrees/create — creates it and records the owner, then provisions its dependencies in the BACKGROUND. Read readinessWarning on the reply (it is present on BOTH a 200 and a 202): a 200 does not mean usable, and a 202 status: 'creating' means path is not yet a place to work — poll GET /worktrees/create/<ledgerId> until it reports ready
git worktree remove POST /worktrees/:id/remove — retires it reversibly into .trash, keeping every floor that guards WORK (uncommitted, ignored, unmerged) or a running job. A worktree's own proven owner asking to give it up is not held by the "is somebody still using this?" floors — it just answered them
git worktree lock / unlock POST /worktrees/:id/pin — which now sets the real git lock
move / prune / repair nothing — the app does these itself

Nothing an agent can do lifts the TYPED refusal. The lockdown rule is evaluated above every escape hatch and reads no grant, so not the pause an agent can open for itself, not the machine-wide approval window, not a repo exclusion, and not an environment flag gets a typed git worktree mutation through. That is deliberate: the change was prompted by an agent using a sanctioned pause to get past the earlier, narrower version of this rule.

The one way through is you. If the app genuinely cannot serve a repo, the agent asks for approval and you get an inbox card; clicking Approve opens a short window for that one session in which the raw commands work. It never opens by itself, and it never covers git worktree remove --force, which destroys a live worktree that is very likely another agent's.

The same refusal is enforced a second time by the git wrapper on the agent's PATH, so a worktree command fired from inside a script an agent wrote is caught too, not just one it typed.

That second layer is a rail, not a wall — and it is the only one covering the script-borne case. The hook only ever sees a command the agent TYPES, so a git worktree add buried in a script is stopped by the PATH shim alone. The shim is coarser by construction: it stands down on AMC_DISABLE_WORKTREE_LOCKDOWN=1 (an inline env prefix sets it) and on the bare presence of ~/.claude/.worktree-lockdown-breakglass.json, with no session match and no expiry check — unlike the hook, which scopes a break-glass window to one session and honours its expiry. So read the "not an environment flag" guarantee above as covering the typed command; for the script-borne path, the hook is the layer of record and the shim is an operator-disablable backstop.

A repo the app does not manage gets the way out named, not a dead end. There POST /worktrees/create answers 404, so every refusal on the path says so: the shim's two worktree refusals and the 404 itself name both switches a raw add meets (AMC_ALLOW_RAW_WORKTREE_ADD=1 and AMC_DISABLE_WORKTREE_LOCKDOWN — one alone fails) and point the agent at you: add the folder as a project, or approve its POST /git-guardrails/approval-request. Neither guard got looser; only what they say changed, after an agent lost 25 minutes bouncing between refusals that each pointed at a door that did not exist for its repo.

How it behaves

What it blocks

When enabled, these operations are gated in a spawned session.

Wrapping a command doesn't hide it (fixed 2026-09-03). Everything below is caught the same way when it's handed to another interpreter to run — bash -c "…", sh -c, powershell -Command, a command piped into a shell, or a git call made from inline node -e code. Until this was fixed those five shapes were let through, so a push to master could slip past on any machine whose only protection was this hook. Writing about a git command is still fine: text you print, log, or save to a file is data, and inline program code is only inspected when it actually has a way to run something.

Nor does a change of folder that is easy to miss (fixed 2026-09-25). A cd inside ( … ), behind builtin or command, or run by eval / Invoke-Expression, and a folder handed to env -C, are followed like a plain cd; code fed to a shell through eval, Invoke-Expression, a heredoc or a here-string is judged in the folder where it really runs.

  1. Commit / write while on a protected branch — any history-changing git command (commit, reset, rebase, merge, cherry-pick, a branch delete, …) while the repo's checked-out branch is master or main.

  2. Push that targets a protected branch — e.g. git push origin master or git push origin HEAD:main, from any branch.

  3. Push that would send a local-only ready-to-merge tag to a remote — the app marks a branch "ready" with a local note (a branch description), never a pushed git tag, so a ready-to-merge tag is a per-worktree relic. Pushing one — by name, refs/tags/…, a force +, or the --tags / --follow-tags / --mirror sweep flags — leaks it to everyone on the next git fetch, so it's blocked. Deleting such a tag from the remote (cleanup) stays allowed.

  4. Push without a ready-to-merge tag (opt-in, default off) — when you turn on the extra "ready-to-merge gate", even a feature-branch push is refused unless the branch carries a current, SHA-bound ready-to-merge marker whose SHA matches the exact commit you're pushing. This enforces a "verify, then tag, then push" workflow. It covers gh pr create as well as git push — publishing the same commits as a pull request is the same act — so an untagged branch cannot be opened as a PR either. Either escape hatch lifts both commands the same way: an approval window (a human grant lifts everything) or an excluded repo.

  5. Destructive git stash — push / pop / drop / clear / apply / save and bare git stash (which is an implicit push). These can silently swallow or reapply another agent's uncommitted work. Read-only git stash list / git stash show are allowed.

  6. Editing a file while the repo is on a protected branch — the same rule as #1, but for the agent's file-editing tools, not just shell git.

  7. A shell command that writes a file into a protected checkout — the shell half of #6. Gating only the edit tools left an obvious way around them: a > redirect, curl -o, tee, cp/mv, or a PowerShell write cmdlet aimed at the master checkout. An agent's working directory is the base checkout even when its real work lives in a worktree, so a stray relative path lands in master — exactly how untracked junk files kept appearing in the repo root. Windows has a nastier version: inside a Bash command an unquoted M:\tmp\out.png has its backslashes eaten and collapses to a relative name, created right where the agent is standing. (Quote it, or use forward slashes, and it writes where you meant.)

  8. Closing pull requests destructively — closing a PR throws work away, and doing it in bulk throws it away faster than anyone can check. A loop, a pipe, a list read from a file, or simply two numbers in one command is refused outright with no override — that exact shape once nearly destroyed ~58 PRs' worth of real code, after an agent tagged 131 of them "redundant" from a heuristic. Closing one PR is allowed, but only with a marker naming that exact number, so every close is a separate deliberate act.

  9. Killing or restarting the app from a shell — Omniscio already refuses agent-driven restarts through its own controls, but a shell command could route around them. One did: it found the app by its control port and killed it, taking 22 live sessions down with it, twice. Tidying up your own dev or test instance is untouched — only a blanket kill, or pinpointing the app by its port, is refused.

  10. Scheduling a task that pops a console window — a scheduled task that runs a script directly draws a black window on your screen every time it fires. Omniscio ships a repair job for this, but it cannot save a one-shot task, which fires long before any repair runs. So it is caught at creation, with the one-line fix included in the message.

About #7–#10 and false alarms. These run before every shell command, so they are tuned to stay quiet. #7 looks only at write targets you typed explicitly, judges each one in the folder its own part of the command runs in, and stands down when it cannot be certain — a path the shell hasn't filled in yet ($TMP, ~/x, %TEMP%), a folder your environment names (cd "$TMP"), a > that is really text inside quotes or a script body, a file the repo already ignores, or a cd into a worktree first. The one doubt it no longer gives you: a relative write after a folder the command works out for itself (cd "$(…)", cd -) is refused, because that folder could be master — give the write an absolute path, or cd to a literal folder first. #8–#10 were tuned the same way. Between them they were checked against more than 51,000 real past commands, and every wrong alarm found was fixed — including one where removing a scheduled task was mistaken for creating one, and another where tidying up your own test instance looked like killing the app.

Each of #9 and #10 has a one-time override you can add to a single command, and #8's single-PR form has its per-PR marker. #8's bulk form deliberately has none — the whole failure mode is an agent talking itself into a batch.

Advisory vs. required

Two enforcement levels (in the Dev Pipeline panel → Setup, or Settings → Features):

  • Required (hard-block) — the default. A gated command is refused; the agent sees a short explanation and can't run it.
  • Advisory (warn-only) — the command is allowed, but the agent gets a warning on stderr explaining why it's risky. Useful for trying the feature without changing what actually happens.

You can also set which branch names count as protected (a comma-separated list, default master + main), and opt into the extra ready-to-merge push gate — all from the same section.

When an agent is blocked

The block surfaces to the agent as a short, friendly message naming the rule, the reason, and the branch/repo — plus how to override it. For example, a blocked push prints roughly:

git-guardrails BLOCKED: push-to-protected
  git push targets a protected branch (master/main) directly —
  pushing straight to a shared branch is gated
  branch: master   repo: C:/code/my-project
Do NOT ask the user to run this command in a terminal. Ways forward:
  1. Work on a worktree branch: POST http://127.0.0.1:19519/worktrees/create
  ...

The wording matters more than it looks. It used to name only a JSON file path, so a blocked agent — with no sanctioned way to ask — would improvise and tell you to open a terminal and run the git command yourself. It now names every route forward and says explicitly not to hand the command back to you — and it is kept deliberately SHORT: every block message lands in an agent's context, so a length ratchet (tests/unit/lint/guard-message-length.test.ts) caps each refusal at roughly two lines.

Overriding (escape hatches)

Two overrides short-circuit the guardrails to "allow", driven from the Git guardrails controls (Settings → Features or the Dev Pipeline panel) and stored in the exact file formats the existing PowerShell "block-master" guards already use.

These override files are a RAIL, not a sandbox — be honest about what that means. The guardrails are a policy layer an agent is expected to respect, not a boundary that can hold against one that does not. Since 2026-09-25 the guard refuses an agent's write, move or delete of ~/.claude/.master-approval.json, ~/.claude/.master-block-exclusions.json, the PR-push grants in ~/.claude/.push-approvals.json and the installed bundle under ~/.claude/hooks/developer-guardrails/ at every enforcement level (rule auth-artifact-write), and it honours a window only in the shape the app and approve-master.ps1 write it — since 2026-09-26 the same for a per-session pause, the agent's self-pause (never past its 10 minutes) and the worktree break-glass, and a hard link to any of these files is refused too. But it judges a command by reading it: a write through a form it does not model yet (truncate, or code run by an interpreter such as perl -i) still gets through, so an agent with a shell can still forge one — an in-place sed -i typed as its own command is no longer one of them (one run through find -exec or xargs still is). That residual has been carried openly since the per-session pause shipped, and closing it fully needs the in-git allowlist flip, not a hook rule. (The repo's own contract states this the same way: "a rail, not a sandbox … one with a shell can write the break-glass file directly — the same residual every override file has carried".)

Why that residual matters, and why it is still the right shape: on 2026-08-25 an agent blocked from editing master issued itself a permission slip and retried — ~/.claude is not a git repo, so the branch check came back empty and nothing objected. The answer is that opening a window is a deliberate, attributable act with a narrow sanctioned form (the app's own controls, or a short -Minutes 1 self-lift for one read-only op, revoked immediately), and an agent that writes one to get past a refusal is violating the rule regardless of whether anything stopped it. Do not read the absence of a refusal as permission.

  • Scoped to one agent (optional). A window may name a single agent session; that agent gets through and every other agent on the machine stays blocked for the window's whole life. Before this, one approval granted for one operation opened every guardrail for the whole fleet for up to four hours. A window with no name is still deliberately machine-wide.

  • Pause the guardrails for a while — a time-boxed window (~/.claude/.master-approval.json: its expiry, when and by whom it was granted, and for how many minutes — a file missing any of those is not a window). "Pause for N minutes" opens it (1–240, with a live countdown); "Resume" closes it. While it's active, everything is allowed — use it for a deliberate, user-authorized burst of protected-branch work.

  • Exclude a repo — a per-repo list (~/.claude/.master-block-exclusions.json, {"repos": ["C:/path/to/repo", …]}). Add or remove a repo from the app; a listed repo (or one nested under it) is never guarded.

Every control that LOWERS the guardrail asks you first (2026-08-27). Pausing, exempting a folder, and switching from Block to Warn only each show a confirmation that names the real scope — "for you AND every AI session on this computer", "everything inside <path>". Tightening back up needs no confirmation. Until then it was the other way round: the only confirmation was on removing an exemption, i.e. the one action that RESTORES protection.

A whole drive is refused as an exemption. A listed folder exempts everything inside it, so typing C:\ would have silently unguarded every repository on the drive while reading as one more unremarkable row in the list. The refusal lives in the service, not just the box, so the same answer comes back through the app, the API and an agent alike.

These two controls stay human-only. Because a machine-wide pause or a repo exclusion lowers the guardrail everywhere, the app never exposes either to the CLI control server or the mobile/web bridge. (You can still hand-edit the JSON files, or use the PowerShell helpers, exactly as before.)

An agent has its own, much narrower path — added because the strict version had a cost you were paying without seeing it: an agent you had already told to go ahead would get blocked, have no way to act, and hand the work back to you as a command to run by hand. It can now:

  • Re-run one command with a CLAUDE_ALLOW_MASTER=1 prefix, when you approved that exact command in conversation. It says so on-screen every time it uses it.
  • Open a short window for itself — 10 minutes, 3 an hour, always with an inbox note.

Neither lifts a push to a remote or a destructive stash. Those, and anything longer, still come to you as a one-click Approve card.

Limits (v1)

  • Guards the sessions Omniscio spawns, not a plain terminal Claude session you start yourself. For that, turn on the machine-wide sibling — Developer guardrails — which installs this same engine into your global Claude config so every Claude Code session on the machine is covered. It is opt-in (off by default), and it shares this feature's pause and exclusion escape hatches.
  • Doesn't guard an SSH/remote session whose remote checkout has no hook.
  • It's a shell-command rail — deliberate obfuscation can evade it (see "not a sandbox").
  • Script-file reading goes one level deep: it opens the script your command runs, but not a further script that script runs. Following the chain without limit would mean unpredictable disk reads before every single command.
  • It recognises git however it's spelled on the command line — git, git.exe, git.cmd, a full path like C:\Program Files\Git\bin\git.exe, and PowerShell's & "<path>" call form all get the same rules. (Until 2026-08-25 only the bare word git was recognised, so the exe and full-path spellings slipped past every rule unguarded — fixed and covered by tests.) Look-alike tools such as gitk and git-foo are still left alone.

For agents

How it works (for agents with repo access)

  • The check is a Claude Code PreToolUse hook — resources/git-guardrails/git-guardrails.mjs in the repo (repo access required) — self-contained Node code (zero app imports) that Claude Code runs before every Bash / Edit / Write tool call. It is four modules — the entry above plus guard-shell (the command model), guard-rules (the rules that are not about a branch) and guard-decision (violation → verdict) — re-exported from the entry, so it is still one import and one process. A PreToolUse hook is used deliberately: it fires even in bypassPermissions / autonomous mode, where an Omniscio-side permission card would be auto-approved and never seen. Exit code 2 blocks; 0 allows (warn writes to stderr and still exits 0).
  • Omniscio wires it in at spawn without touching your repo: for each session it launches, it writes a session-private settings file carrying only the hooks.PreToolUse entry and passes it to the CLI with --settings <path> (which merges with your own settings), and sets the hook's config in the AMC_GIT_GUARDRAILS environment variable. This mirrors how Omniscio writes a private .mcp.json and passes it via --mcp-config. The wiring lives in src/main/process/git-guardrails-spawn.ts and src/main/process/spawn-cluster-manager.ts (repo access required).
  • Fail-open by design. Any error in the hook, or a missing bundled script, or a broken config → the operation is allowed. A safety rail must never wedge a session. The one exception: a tool call whose details have not fully arrived within 30 seconds is refused (payload-incomplete) — nothing in it could be checked, so it is never waved through; run the same call again.
  • Gated + solo-first. Injection only happens when gitGuardrailsEnabled is on (the default; the spawn gate keys on the setting directly, so turning it off truly disables it); it's a per-user setting in v1. Org-enforced / admin-set team policy is a documented follow-up (the desktop has no org/team awareness yet).

The full behavior lock lives in git-guardrails-contract.md, under .claude/memory/contracts/ in the repo (repo access required) — it is the authoritative spec this page summarizes.

Related

  • Git guardrails (part 2) — the continuation of this page. Developer guardrails is the machine-wide sibling: the same discipline, but written into your global Claude settings so it covers every session on the computer rather than just the ones Omniscio spawns.

Last verified 2026-10-04