ControlLoopStart free trial

Review what a clone installs and runs before it merges

ControlLoop reviews the files that silently install tools and run scripts every time a repo is opened or committed to, and flags any new, changed, or unpinned entry in the pull request.

Team: 7 days free, then $199 a month per organization. Cancel any time. Compare plans →

These files run before anyone reads the code

Opening a repository in a dev container installs the extensions and features its devcontainer.json names and runs its lifecycle commands, initializeCommand on the developer's own machine before the container exists. pre-commit runs its hook repositories on every commit, and Neovim, Vim, and Emacs can run a startup file committed at the project root when the folder opens.

A tag can be moved after review. An extension with no @version, a feature on :1 or :latest, or a pre-commit rev that names a tag instead of a commit installs whatever its publisher ships next, on every machine that uses the repository.

What ControlLoop reports on the pull request
.devcontainer/devcontainer.json
  "customizations": { "vscode": {
+    "extensions": ["ms-python.python"]
  } },
+  "initializeCommand": "bash .devcontainer/init.sh"
policy.unpinned-agent-componentMedium

Added: editor extension ms-python.python has no version pin, so the next clone installs whatever version is current.

policy.agent-hook-command-changeHigh

Added: initializeCommand for Dev Containers runs bash .devcontainer/init.sh on the developer's machine. No approval is recorded.

×
ControlLoop gateDeployment blocked
EXIT 1

What ControlLoop reports

Pinned means exact

An extension is pinned by @major.minor.patch, a devcontainer feature by an exact version or a sha256 digest, a pre-commit repository by a full commit SHA, and a JetBrains plugin when its minimum and maximum versions match. Anything else is reported as unpinned.

Commands and startup files by digest

Lifecycle commands and local pre-commit hooks are reported when they are added or changed, and a committed script they run is tracked by SHA-256. Editor startup files are never parsed; a changed file is reported by its digest.

Read, never run

Nothing is installed, pulled, cloned, or run. The gate reports the change for a person to review; it does not judge whether an extension is safe.

How it works

  1. 01

    Re-record the baseline

    Scan with CLI 0.7.0 or later. A baseline from an earlier version has none of these entries, so the first scan against it reports each one as added.

  2. 02

    Gate every pull request

    A pull request that adds an extension, moves a pre-commit rev to a tag, or edits a lifecycle command gets one line per change, with the file it came from.

  3. 03

    Pin it, or approve it

    Pin the entry to an exact version, digest, or commit and the unpinned finding clears. A reviewed command is recorded as an approval, which turns the block into a note.

Questions

Does ControlLoop install the extensions or run the commands?

No. The files are read as text. Nothing is installed, pulled, cloned, or run, and editor startup files are recorded by path and SHA-256 without being parsed.

Which files are read?

devcontainer.json in .devcontainer/, one folder below it, or .devcontainer.json; .vscode/extensions.json; *.code-workspace files; .pre-commit-config.yaml; .nvim.lua, .nvimrc, .exrc, .lvimrc, .dir-locals.el, and .dir-locals-2.el at the repository root; and .idea/externalDependencies.xml.

Does this replace an editor's extension allowlist?

No. Device-level controls such as VS Code's extensions.allowed policy cover what a person installs by hand. The gate covers what the repository declares, so the two work together.

Go deeper

Read the guide: Claude Code hooks and permissions: what a pull request can grant an agent