Skip to content

The Drift Log · Governing agents that write code

Blocking Agent Commits that Introduce Undeclared Runtime I/O

31 October 2026 · 3 min read · 591 words · established

A crystal shard held at a red-lit gateway in a black wall

Prevent coding agents from bypassing side-effect boundaries by validating AST purity invariants before write proposals touch disk.

An AI coding agent tasked with fixing an edge-case configuration bug will often alter a helper function like resolvePermissions(user: User): PermissionSet. To handle an undocumented edge case where role metadata is missing, it inserts a synchronous call to fs.readFileSync or an outbound fetch directly into the helper body.

The unit tests pass. The agent’s local test runner operates with ambient OS permissions, so reading from disk or touching a local socket succeeds. The diff looks small: eight lines added to an existing file. But a function that was previously pure and executed entirely in memory now performs undeclared runtime I/O, blocks the event loop, and ties domain logic directly to ambient platform state.

If your code review automation only checks syntax, formatting, and unit test pass rates, these side effects slip into your main branch.

The Failure Mode of Agentic Patching

AI coding agents optimize for a narrow target: satisfying the immediate prompt while making the immediate test suite pass. They do not have an architectural model of your application's side-effect boundaries. When an agent discovers it needs external state to complete a calculation, it takes the shortest syntactic path. It imports standard library I/O modules, reads environment variables, or opens network sockets directly inside pure computation paths.

This introduces three immediate production failures:

  1. Broken portability: A utility function designed to run in browser workers, edge runtimes, or isolated sandboxes suddenly crashes on missing Node.js runtime primitives.
  2. Hidden latency and concurrency bottlenecks: Synchronous I/O operations introduced into hot loops degrade throughput under production load without failing any functional tests.
  3. Implicit security boundary degradation: Functions that accepted sanitized inputs now pull unsanitized data from disk or the network, bypassing edge validation layers.

Telling the model in a prompt to "avoid side effects in helper functions" is insufficient. Prompts are advisory; agent governance requires mechanical enforcement.

Catching Undeclared I/O at the Build Intent Gate

The correct control point is not the model prompt, nor is it the final human pull request review. It is the build gate between an agent deciding to write and that write being committed to the workspace.

As detailed in our doctrine on governing code agents at the build intent gate, write proposals must satisfy strict architectural invariants before reaching version control. When an agent proposes a patch, the gate must parse the abstract syntax tree (AST) of the target file and reject writes that violate declared module boundaries.

// The intent gate rejects this diff when applied to a pure module:
export function calculateEffectiveRate(tier: string): number {
  // Agent-injected I/O inside pure transformation logic
  const override = fs.readFileSync('/etc/pricing/overrides.json', 'utf8');
  const rates = JSON.parse(override);
  return rates[tier] ?? defaultRates[tier];
}

If a file or directory is designated as pure domain logic (such as files under src/domain/ or modules tagged with an explicit purity invariant), the gate walks the proposed AST against three rigid checks:

  1. Import specifier validation: The parser inspects every ImportDeclaration. Any import referencing I/O namespaces—such as fs, node:fs, net, node:net, child_process, or third-party database and HTTP drivers—fails the gate immediately.
  2. Ambient global prohibition: The parser checks Identifier and MemberExpression nodes for references to runtime globals that provide ambient I/O, including process.env, fetch, localStorage, or WebSocket.
  3. Dynamic evaluation blocking: Any CallExpression matching eval, dynamic import(), or require() within a pure scope node triggers a terminal rejection.

A minimal AST validator implemented at the gate illustrates the mechanism:

import ts from 'typescript';

export function assertModulePurity(sourceFile: ts.SourceFile): string[] {
  const blockedModules = new Set(['fs', 'node:fs', 'net', 'node:net', 'http', 'node:http']);
  const violations: string[] = [];

  function visit(node: ts.Node) {
    if (ts.isImportDeclaration(node)) {
      const specifier = (node.moduleSpecifier as ts.StringLiteral).text;
      if (blockedModules.has(specifier)) {
        violations.push(`Illegal import '${specifier}' in pure module.`);
      }
    }

    if (ts.isIdentifier(node) && node.text === 'fetch' && !node.parent.getSourceFile().isDeclarationFile) {
      violations.push("Illegal reference to global 'fetch' in pure module.");
    }

    ts.forEachChild(node, visit);
  }

  visit(sourceFile);
  return violations;
}

When an agent proposes an edit that introduces undeclared I/O, the gate rejects the intent and returns the exact invariant violation as a structured error. The agent is forced to correct its design—accepting the required data as an explicit function parameter—before any bytes are written to disk.

Keep reading

Next in the log

The Strategic Master Library · written and reviewed under the house's own epistemic rules: nothing claimed that we cannot show.