Skip to content

Binding CLI stubs materialize but exec fails with ENOENT (127) — agents cannot invoke bindings #1885

Description

@ragnorc

Problem

Registered bindings produce their kernel command stubs in the VM, but executing one fails with exec-time ENOENT — the shell reports command not found, process.exec returns 127 — so an agent can never invoke a binding. This breaks the core bindings flow (the crash-course example shape, passed via bindings: [group]).

The stubs demonstrably exist and are executable:

$ which agentos-weather          → /bin/agentos-weather
$ ls /bin/agentos*             → /bin/agentos  /bin/agentos-weather
$ head -c 40 /bin/agentos-weather → #!/bin/sh
                                  # kernel command stub
$ ls -la /bin/agentos-weather    → -rwxr-xr-x 1 0 0 32 …

$ agentos-weather forecast --city Paris           → error: command not found: agentos-weather

sh maps a missing-interpreter/registration exec ENOENT to command not found — the stub file resolves, its execution doesn't. (Side note: the stubs land in /bin, while the bindings docs say /usr/local/bin/agentos-{name}.)

Reproduced on 0.2.15 and 0.2.16-rc.1, macOS arm64, Node 25, on both the agentOS() actor path and the Core path (AgentOs.create()), with explicit full-allow permissions including binding: 'allow'. Config forwarding was ruled out: bindings is in agentOsOptionKeys, ensureVm spreads it into AgentOs.create, and collectBindingBootstrapCommands emits agentos + agentos-<group> into bootstrapCommands.

Reproduction

import { AgentOs } from '@rivet-dev/agentos';
import { z } from 'zod';

const group = {
  name: 'weather',
  description: 'Weather data bindings',
  bindings: {
    forecast: { description: 'Get a forecast', inputSchema: z.object({ city: z.string() }).strict(), execute: async () => ({ temperature: 22 }) },
  },
};

const vm = await AgentOs.create({
  bindings: [group],
  permissions: { fs: 'allow', childProcess: 'allow', process: 'allow', env: 'allow', binding: 'allow', network: 'deny' } as never,
});
const r = await vm.process.exec('agentos-weather forecast --city Paris', { captureStdio: true });
console.log(r.exitCode); // 127 — expected: 0 with {"ok":true,"result":{...}} on stdout

Expected behavior

  • Executing a registered binding stub dispatches to the host execute() and returns its JSON envelope (exit 0), per the bindings docs.
  • If registration requires something beyond bindings: [...] + binding: 'allow', the failure should be loud at VM creation, not ENOENT at call time inside the agent's turn.
  • Docs and implementation agree on the shim path (/bin vs /usr/local/bin).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions