ComposioHQ/composio · error · Error

Interactive permission prompts are disabled in this environm

Error message

Interactive permission prompts are disabled in this environment (CI/VITEST, or COMPOSIO_DISABLE_PERMISSION_UI); cannot collect an approval for ${params.toolSlug}.

What it means

The CLI's interactive permission prompt cannot be shown because the environment is detected as non-interactive (CI, Vitest) or COMPOSIO_DISABLE_PERMISSION_UI is set. The tool-approval flow needs a human decision for the given toolSlug, so it throws rather than silently approving or hanging.

Source

Thrown at ts/packages/cli/src/services/tool-permissions.ts:1047

        reject(new Error('Unable to allocate permission callback port.'));
        return;
      }

      void open(`http://127.0.0.1:${port}/?token=${encodeURIComponent(token)}`, {
        wait: false,
      }).catch(error => {
        server.close();
        reject(error);
      });
    });
  });

const requestPermissionDecision = async (params: {
  readonly toolSlug: string;
  readonly accountLabel?: string;
}): Promise<PermissionDecision> => {
  if (await Effect.runPromise(isInteractivePermissionUiDisabled)) {
    throw new Error(
      `Interactive permission prompts are disabled in this environment (CI/VITEST, or COMPOSIO_DISABLE_PERMISSION_UI); cannot collect an approval for ${params.toolSlug}.`
    );
  }

  // Prefer the bundled macOS native sidecar when it is available. The browser
  // prompt remains the cross-platform fallback and is only opened when the
  // native sidecar is missing or fails before returning a decision.
  const nativeDecision = await requestNativeUiPermissionDecision(params).catch(() => undefined);
  if (nativeDecision === 'allow_once' || nativeDecision === 'allow_session') return nativeDecision;
  if (nativeDecision === 'deny' || nativeDecision === 'dismissed') return 'deny';
  return requestPermissionInBrowser({ ...params, agent: await detectNativeUiCallerAgent() });
};

export const gateToolExecution = (params: GateParams) =>
  Effect.gen(function* () {
    const state = resolveGateState(params);
    if (state === 'skip') return;

View on GitHub (pinned to 64b1b85502)

Solutions

  1. Set an explicit non-interactive permission policy (e.g. allowlist/deny rules or a pre-configured approval mode) so no interactive prompt is needed
  2. Unset COMPOSIO_DISABLE_PERMISSION_UI and ensure a TTY is available if interactive approval is intended
  3. In tests, mock/stub the permission decision layer rather than relying on prompts

Example fix

// before
await cli.run(commandRequiringApproval);
// after
process.env.COMPOSIO_TOOL_PERMISSIONS = 'allow'; // or configure allowlist
await cli.run(commandRequiringApproval);
Defensive patterns

Strategy: validation

Validate before calling

import { Effect } from 'effect';
const disabled = await Effect.runPromise(isInteractivePermissionUiDisabled);
if (disabled) { /* set non-interactive policy or skip */ }

Try / catch

catch (e) { if (/Interactive permission prompts are disabled/.test(String(e.message))) { /* apply fallback policy */ } else throw e; }

Prevention

When it happens

Trigger: Calling an API that ends in requestPermissionDecision for a tool while running under CI/VITEST env detection or with COMPOSIO_DISABLE_PERMISSION_UI set, without a non-interactive approval policy in place.

Common situations: Running the Composio CLI or its tests in CI pipelines, Docker containers without a TTY, or headless environments where the permission UI was explicitly disabled.

Related errors


AI-assisted analysis of ComposioHQ/composio@64b1b85502 (2026-08-28). Data as JSON: /api/errors/518d7b3e5455856a. Report an issue: GitHub.