The Problem
AI coding agents are powerful because they can use tools—executing shell commands, reading files, making API calls, running tests, and more. But there’s a critical failure mode:
Agents assume tools are available when they’re not.
Common Failure Scenarios
Scenario 1: Missing CLI Tools
Agent: "I'll use ripgrep to search the codebase"
> rg "function authenticate"
Error: command not found: rg
Agent: "Let me try again with rg..."
> rg "function authenticate"
Error: command not found: rg
Agent: "Hmm, still trying rg..."
> rg --json "function authenticate"
Error: command not found: rg
Cost: 3+ failed attempts, wasted tokens, no progress
Scenario 2: Unavailable API Features
Agent: "I'll use the GitHub API to create a pull request"
> gh pr create --title "Fix bug"
Error: gh: command not found
Agent: "Let me authenticate first..."
> gh auth login
Error: gh: command not found
Cost: Multi-step workflow fails midway through, partial work wasted
Scenario 3: Environment-Specific Limitations
Agent: "I'll deploy using gcloud CLI"
> gcloud run deploy api-service
Error: gcloud: command not found
User: "We're on AWS, not GCP"
Agent: "Oh, let me use gcloud differently..."
> gcloud --project=... run deploy
Error: gcloud: command not found
Cost: Wrong deployment approach, confusion, manual intervention needed
Why This Happens
LLMs are trained on vast codebases where tools like git, npm, docker, gh, gcloud, etc. are commonly available. They assume standard tools exist without checking.
Additionally:
- No built-in capability detection: LLMs don’t probe environment before using tools
- Optimistic execution: Agents try commands first, discover failures later
- Poor error recovery: After tool failures, agents often retry the same approach
- Context loss: By the time tool unavailability is clear, significant tokens are spent
The Cost
For individual workflows:
- 3-5 failed attempts before giving up
- 2,000-5,000 wasted tokens per failure
- 2-5 minutes of developer time debugging
For teams (100 workflows/week):
- 20-30 tool-related failures weekly
- 100,000-150,000 wasted tokens monthly
- 5-10 hours of cumulative debugging time
The Solution
Systematic tool detection: Probe for required capabilities before starting workflows.
Core Pattern
// 1. Define tool requirements
const requiredTools = [
{ name: 'git', check: 'git --version' },
{ name: 'node', check: 'node --version' },
{ name: 'npm', check: 'npm --version' },
];
const optionalTools = [
{ name: 'gh', check: 'gh --version', alternatives: ['hub', 'manual PR creation'] },
{ name: 'docker', check: 'docker --version', alternatives: ['podman'] },
];
// 2. Detect capabilities
const capabilities = await detectTools([...requiredTools, ...optionalTools]);
// 3. Fail fast if critical tools missing
if (!capabilities.git || !capabilities.node) {
throw new Error(
`Missing required tools: ${getMissingTools(capabilities, requiredTools).join(', ')}\n` +
`Please install: https://docs.project.com/setup`
);
}
// 4. Provide capability context to agent
const context = `
Available tools:
${formatCapabilities(capabilities)}
Use ONLY the available tools listed above.
For missing optional tools, use listed alternatives.
`;
// 5. Execute workflow with capability awareness
await runAgentWorkflow({ context, capabilities });
Implementation
Step 1: Define Tool Registry
Create a registry of all tools your workflows might use:
// tools-registry.ts
export interface ToolDefinition {
name: string;
check: string; // Command to verify availability
required: boolean;
alternatives?: string[];
installDocs?: string;
}
export const TOOL_REGISTRY: ToolDefinition[] = [
// Version control
{
name: 'git',
check: 'git --version',
required: true,
installDocs: 'https://git-scm.com/downloads',
},
// Package managers
{
name: 'npm',
check: 'npm --version',
required: true,
alternatives: ['yarn', 'pnpm'],
},
{
name: 'yarn',
check: 'yarn --version',
required: false,
alternatives: ['npm', 'pnpm'],
},
// Code search
{
name: 'ripgrep',
check: 'rg --version',
required: false,
alternatives: ['grep', 'ag (silver searcher)'],
},
// GitHub CLI
{
name: 'gh',
check: 'gh --version',
required: false,
alternatives: ['hub', 'manual PR creation via web UI'],
installDocs: 'https://cli.github.com/',
},
// Cloud CLIs
{
name: 'gcloud',
check: 'gcloud --version',
required: false,
alternatives: ['GCP Console', 'Terraform'],
},
{
name: 'aws',
check: 'aws --version',
required: false,
alternatives: ['AWS Console', 'Terraform'],
},
// Container tools
{
name: 'docker',
check: 'docker --version',
required: false,
alternatives: ['podman', 'finch'],
},
// Testing tools
{
name: 'playwright',
check: 'npx playwright --version',
required: false,
alternatives: ['cypress', 'selenium'],
},
];
Step 2: Implement Detection Logic
// tool-detector.ts
import { exec } from 'child_process';
import { promisify } from 'util';
const execAsync = promisify(exec);
export interface ToolCapability {
name: string;
available: boolean;
version?: string;
alternatives?: string[];
}
export interface CapabilityReport {
tools: Record<string, ToolCapability>;
missingRequired: string[];
missingOptional: string[];
}
export async function detectToolCapability(
tool: ToolDefinition
): Promise<ToolCapability> {
try {
const { stdout } = await execAsync(tool.check, {
timeout: 2000, // 2 second timeout
shell: '/bin/bash',
});
return {
name: tool.name,
available: true,
version: stdout.trim(),
alternatives: tool.alternatives,
};
} catch (error) {
return {
name: tool.name,
available: false,
alternatives: tool.alternatives,
};
}
}
export async function detectAllCapabilities(
tools: ToolDefinition[]
): Promise<CapabilityReport> {
// Run checks in parallel
const results = await Promise.all(
tools.map((tool) => detectToolCapability(tool))
);
// Build capability map
const capabilityMap: Record<string, ToolCapability> = {};
for (const result of results) {
capabilityMap[result.name] = result;
}
// Identify missing tools
const missingRequired = tools
.filter((t) => t.required && !capabilityMap[t.name].available)
.map((t) => t.name);
const missingOptional = tools
.filter((t) => !t.required && !capabilityMap[t.name].available)
.map((t) => t.name);
return {
tools: capabilityMap,
missingRequired,
missingOptional,
};
}
Step 3: Generate Agent Context
Create clear context for the LLM agent:
// capability-formatter.ts
export function formatCapabilitiesForAgent(
report: CapabilityReport
): string {
const available = Object.values(report.tools)
.filter((t) => t.available)
.map((t) => ` ✅ ${t.name} (${t.version})`)
.join('\n');
const unavailable = Object.values(report.tools)
.filter((t) => !t.available)
.map((t) => {
const alts = t.alternatives
? ` → Use instead: ${t.alternatives.join(' or ')}`
: '';
return ` ❌ ${t.name}${alts}`;
})
.join('\n');
return `
# Available Tools
## ✅ Available (you can use these)
${available}
## ❌ Unavailable (do NOT attempt to use)
${unavailable}
**IMPORTANT**: Only use tools marked with ✅. For tools marked with ❌, use the listed alternatives.
`;
}
Step 4: Integrate with Agent Workflows
// agent-workflow.ts
import { detectAllCapabilities } from './tool-detector';
import { formatCapabilitiesForAgent } from './capability-formatter';
import { TOOL_REGISTRY } from './tools-registry';
export async function runAgentWorkflow(task: string) {
// 1. Detect capabilities upfront
console.log('🔍 Detecting available tools...');
const capabilities = await detectAllCapabilities(TOOL_REGISTRY);
// 2. Fail fast if required tools missing
if (capabilities.missingRequired.length > 0) {
throw new Error(
`Missing required tools: ${capabilities.missingRequired.join(', ')}\n` +
`Install them before running this workflow.`
);
}
// 3. Warn about missing optional tools
if (capabilities.missingOptional.length > 0) {
console.warn(
`⚠️ Optional tools unavailable: ${capabilities.missingOptional.join(', ')}`
);
console.warn(' Workflow will use alternatives where possible.');
}
// 4. Generate capability context for agent
const capabilityContext = formatCapabilitiesForAgent(capabilities);
// 5. Execute workflow with capability awareness
const prompt = `
${capabilityContext}
---
# Task
${task}
Remember: Use ONLY the tools marked as available above.
`;
return await executeAgentTask(prompt, capabilities);
}
Best Practices
1. Detect Early, Fail Fast
Run detection before starting expensive operations:
// ✅ Good: Detect before starting
async function deployToProduction() {
const capabilities = await detectCapabilities();
if (!capabilities.gcloud.available) {
throw new Error('gcloud CLI required for deployment');
}
// Now run deployment workflow
await runDeployment();
}
// ❌ Bad: Discover mid-deployment
async function deployToProduction() {
await buildDockerImage(); // Takes 5 minutes
await runTests(); // Takes 3 minutes
await exec('gcloud run deploy...'); // FAILS: command not found
}
2. Cache Detection Results
Tool availability rarely changes during a session:
// Cache capabilities for session
let cachedCapabilities: CapabilityReport | null = null;
export async function getCapabilities(): Promise<CapabilityReport> {
if (cachedCapabilities) {
return cachedCapabilities;
}
cachedCapabilities = await detectAllCapabilities(TOOL_REGISTRY);
return cachedCapabilities;
}
// Invalidate cache if needed
export function invalidateCapabilityCache() {
cachedCapabilities = null;
}
3. Provide Actionable Error Messages
// ❌ Bad: Vague error
if (!capabilities.gh.available) {
throw new Error('gh not found');
}
// ✅ Good: Actionable error
if (!capabilities.gh.available) {
throw new Error(
`GitHub CLI (gh) is required for this workflow.\n\n` +
`Install it:\n` +
` macOS: brew install gh\n` +
` Linux: https://cli.github.com/manual/installation\n` +
` Windows: winget install GitHub.cli\n\n` +
`Or use alternative: Create PR manually via GitHub web UI`
);
}
4. Support Graceful Degradation
Use alternatives when optional tools are missing:
if (capabilities.ripgrep.available) {
// Fast: use ripgrep
await exec('rg "pattern" --json');
} else if (capabilities.ag.available) {
// Fallback: use ag (silver searcher)
await exec('ag "pattern" --json');
} else {
// Last resort: use grep (slower but universal)
await exec('grep -r "pattern" .');
}
5. Document Tool Requirements
Make requirements discoverable:
# Project Setup (README.md)
## Required Tools
- **git** - Version control
- **node** (v18+) - Runtime
- **npm** or **yarn** - Package management
## Optional Tools (recommended)
- **gh** - GitHub CLI for PR creation
- Install: `brew install gh` (macOS) or `https://cli.github.com`
- Alternative: Manual PR creation via web UI
- **ripgrep** - Fast code search
- Install: `brew install ripgrep` (macOS) or `https://github.com/BurntSushi/ripgrep`
- Alternative: Uses `grep` if unavailable
Real-World Examples
Example 1: PR Creation Workflow
async function createPullRequest(options: PROptions) {
const capabilities = await getCapabilities();
if (capabilities.tools.gh.available) {
// Best: Use GitHub CLI
console.log('✅ Using GitHub CLI to create PR');
await exec(`gh pr create --title "${options.title}" --body "${options.body}"`);
} else if (capabilities.tools.hub.available) {
// Fallback: Use hub CLI
console.log('⚠️ gh unavailable, using hub instead');
await exec(`hub pull-request -m "${options.title}\n\n${options.body}"`);
} else {
// Manual fallback
console.log('❌ No CLI available. Manual steps required:');
console.log('1. Push your branch: git push origin ' + options.branch);
console.log('2. Visit: https://github.com/owner/repo/compare/' + options.branch);
console.log('3. Click "Create pull request"');
console.log('4. Title: ' + options.title);
console.log('5. Body: ' + options.body);
// Return manual instructions instead of failing
return {
success: false,
manual: true,
instructions: [...],
};
}
}
Example 2: Deployment Workflow
async function deployApplication(env: 'staging' | 'production') {
const capabilities = await getCapabilities();
// Determine deployment method based on available tools
if (capabilities.tools.gcloud.available) {
console.log('✅ Deploying with gcloud (Google Cloud Run)');
return await deployWithGCloud(env);
} else if (capabilities.tools.aws.available) {
console.log('✅ Deploying with aws CLI (AWS ECS)');
return await deployWithAWS(env);
} else if (capabilities.tools.docker.available) {
console.log('✅ Building with docker, manual deploy needed');
await exec('docker build -t app:latest .');
console.log('Image built. Deploy manually to your platform.');
return { success: false, manual: true };
} else {
throw new Error(
'No deployment tools available. Install gcloud, aws, or docker.'
);
}
}
Example 3: Code Search Workflow
async function searchCodebase(pattern: string) {
const capabilities = await getCapabilities();
// Use fastest available search tool
if (capabilities.tools.ripgrep.available) {
// Fastest (100x faster than grep)
return await exec(`rg "${pattern}" --json`);
} else if (capabilities.tools.ag.available) {
// Fast (10x faster than grep)
return await exec(`ag "${pattern}" --json`);
} else {
// Fallback to universal grep (slow but always available)
console.warn('⚠️ Using grep (slow). Install ripgrep for 100x speedup.');
return await exec(`grep -r "${pattern}" .`);
}
}
Integration with Claude Code
Pattern: Add Capability Context to CLAUDE.md
# CLAUDE.md
## Available Tools
This project uses the following tools. Check availability before use:
### Required
- git (version control)
- node v18+ (runtime)
- npm (package manager)
### Optional
- gh (GitHub CLI) - for automated PR creation
- Alternative: Manual PR via web UI
- ripgrep - for fast code search
- Alternative: grep (slower)
- docker - for containerization
- Alternative: Manual builds
**Before using any tool, verify it's available with `which <tool>` or `<tool> --version`.**
Pattern: Claude Code Hooks
Use hooks to detect capabilities automatically:
// .claude/hooks.json
{
"pre-task": "node scripts/detect-capabilities.js"
}
// scripts/detect-capabilities.js
const { detectAllCapabilities } = require('./tool-detector');
const { TOOL_REGISTRY } = require('./tools-registry');
async function main() {
const report = await detectAllCapabilities(TOOL_REGISTRY);
if (report.missingRequired.length > 0) {
console.error('❌ Missing required tools:', report.missingRequired);
process.exit(1);
}
if (report.missingOptional.length > 0) {
console.warn('⚠️ Missing optional tools:', report.missingOptional);
}
console.log('✅ Tool check passed');
}
main();
Measuring Success
Key Metrics
-
Tool-Related Failure Rate
Before: 20-30% of workflows fail due to missing tools After: <5% failure rate Improvement: 80%+ reduction -
Time to First Failure
Before: 30-60 seconds (after significant token usage) After: <5 seconds (immediate detection) Improvement: 90% faster failure detection -
Wasted Token Count
Before: 2,000-5,000 tokens per tool failure After: <100 tokens (early detection) Improvement: 95%+ reduction -
Developer Debugging Time
Before: 2-5 minutes per failure After: <30 seconds (clear error messages) Improvement: 80%+ time savings
Common Pitfalls
Pitfall 1: Assuming Tools Are Available
Problem: Agent assumes gh is available without checking
Solution: Always detect before use
Pitfall 2: Poor Error Messages
Problem: “Command not found” without context
Solution: Provide installation instructions and alternatives
Pitfall 3: Re-detecting Every Time
Problem: Running detection for every operation (slow)
Solution: Cache results for session duration
Pitfall 4: No Graceful Degradation
Problem: Hard failure when optional tools missing
Solution: Implement fallback alternatives
Pitfall 5: Incomplete Tool Registry
Problem: Registry missing tools actually used by workflows
Solution: Audit workflows to build complete registry
Advanced Patterns
Pattern: Feature Flags Based on Capabilities
const features = {
autoCreatePR: capabilities.gh.available,
fastSearch: capabilities.ripgrep.available,
containerBuilds: capabilities.docker.available,
};
// Adjust workflow based on available features
if (features.autoCreatePR) {
await createPRAutomatically();
} else {
await generatePRInstructions();
}
Pattern: Progressive Enhancement
// Start with basic capabilities
let searchSpeed = 'basic';
if (capabilities.ripgrep.available) {
searchSpeed = 'fast';
} else if (capabilities.ag.available) {
searchSpeed = 'medium';
}
console.log(`Code search speed: ${searchSpeed}`);
Pattern: Capability-Aware Prompts
const prompt = `
Available capabilities:
${formatCapabilities(capabilities)}
Task: ${userTask}
Plan your approach using ONLY available tools.
For unavailable tools, use listed alternatives.
`;
Conclusion
Agentic tool detection is a high-impact, low-effort pattern that prevents 80%+ of tool-related workflow failures.
Key Principles:
- Detect early: Check capabilities before starting workflows
- Fail fast: Immediate errors for missing required tools
- Clear messages: Actionable errors with installation instructions
- Graceful degradation: Fallback alternatives for optional tools
- Cache results: Avoid redundant detection checks
The Result: Reliable agent workflows that fail predictably with actionable feedback, not mysteriously mid-execution.
Related Concepts
- AST-Based Code Search – Precision code search using AST patterns (ast-grep)
- Custom ESLint Rules for AI Determinism – Teach LLMs architecture through structured errors
- Playwright Script Loop – Generate validation scripts for faster feedback cycles
- Evaluation-Driven Development – Self-healing test loops with AI vision
- Test Custom Infrastructure – Avoid the house on stilts by testing tooling
- Error Messages as Training Data – Structured errors teach LLMs correct patterns
- Human-First Optimization – Design for human developer experience
- Verification Sandwich Pattern – Establish baseline before and after changes
- Declarative Constraints – Constrain LLM output with declarations

