vercel/ai · error

'HarnessAgent: pass either `activeTools` or `inactiveTools`,

Error message

'HarnessAgent: pass either `activeTools` or `inactiveTools`, not both.'

What it means

resolveHarnessAgentToolFiltering accepts exactly one of `activeTools` (allow-list) or `inactiveTools` (deny-list). Passing both is ambiguous — the library cannot decide whether to intersect or prioritize them, so it throws immediately.

Source

Thrown at packages/harness/src/agent/internal/tool-filtering.ts:22

import type { ToolSet } from '@ai-sdk/provider-utils';

export type ResolvedHarnessAgentToolFiltering<TUserTools extends ToolSet> = {
  readonly activeUserTools: TUserTools;
  readonly builtinToolFiltering?: HarnessV1BuiltinToolFiltering;
};

export function resolveHarnessAgentToolFiltering<
  TAllTools extends ToolSet,
  TUserTools extends ToolSet,
>(input: {
  harness: HarnessV1;
  userTools: TUserTools;
  allTools: TAllTools;
  activeTools: ActiveTools<TAllTools>;
  inactiveTools: ActiveTools<TAllTools>;
}): ResolvedHarnessAgentToolFiltering<TUserTools> {
  if (input.activeTools !== undefined && input.inactiveTools !== undefined) {
    throw new Error(
      'HarnessAgent: pass either `activeTools` or `inactiveTools`, not both.',
    );
  }

  const allToolNames = Object.keys(input.allTools);
  const activeTools = dedupeToolNames({ toolNames: input.activeTools });
  const inactiveTools = dedupeToolNames({ toolNames: input.inactiveTools });
  validateToolNames({
    requestedToolNames: activeTools ?? inactiveTools,
    availableToolNames: allToolNames,
  });

  const userToolNames = Object.keys(input.userTools);
  const activeUserToolNames =
    activeTools != null
      ? userToolNames.filter(name => activeTools.includes(name))
      : inactiveTools != null
        ? userToolNames.filter(name => !inactiveTools.includes(name))

View on GitHub (pinned to 69428b1f8b)

Solutions

  1. Remove one of the two options — keep activeTools to allow-list or inactiveTools to deny-list.
  2. If both lists come from merged configs, convert: inactiveTools = allToolNames.filter(n => !activeTools.includes(n)) and pass only one.
  3. Compute the effective list yourself and pass a single array.

Example fix

// before
new HarnessAgent({ activeTools: ['read', 'write'], inactiveTools: ['shell'] });
// after
new HarnessAgent({ activeTools: ['read', 'write'] }); // or inactiveTools only
Defensive patterns

Strategy: validation

Validate before calling

if (config.activeTools != null && config.inactiveTools != null) throw new Error('use activeTools or inactiveTools, not both');

Try / catch

try { createAgent(config); } catch (e) { if (e.message.includes('not both')) { config = { ...config, inactiveTools: undefined }; createAgent(config); } else throw e; }

Prevention

When it happens

Trigger: Creating a HarnessAgent (or calling toolFiltering config) with both activeTools: ['read','write'] and inactiveTools: ['shell'] set simultaneously.

Common situations: Merging two config objects where each sets a different tool list; spreading defaults that include inactiveTools on top of user-supplied activeTools.

Related errors


AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30). Data as JSON: /api/errors/588e4cb5ad4565be. Report an issue: GitHub.