home / skills / outfitter-dev / agents / stack-debug

This skill helps you diagnose and fix Outfitter stack issues across results, MCP, CLI output, exit codes, and logging.

npx playbooks add skill outfitter-dev/agents --skill stack-debug

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
4.6 KB
---
name: stack-debug
version: 0.1.0
description: Troubleshoots Outfitter Stack issues including Result handling, MCP problems, CLI output, exit codes, and logging. Use when debugging stack-specific issues, unexpected errors, wrong output modes, or when "debug Result", "MCP not working", "wrong exit code", or "logging issue" are mentioned.
context: fork
agent: stacker
allowed-tools: Read Grep Glob Bash(rg *) Bash(bun *)
---

# Stack Debugging

Troubleshoot @outfitter/* package issues.

## Result Issues

### Always Getting Error

**Symptom:** Result is `err` when it should be `ok`.

**Check validation:**
```typescript
const inputResult = validateInput(rawInput);
if (inputResult.isErr()) {
  console.log("Validation failed:", inputResult.error.details);
  return inputResult;
}
```

**Check async:**
```typescript
// BAD: Missing await
const result = getUser(id);  // Promise, not Result!

// GOOD
const result = await getUser(id);
```

### Type Narrowing Broken

**Symptom:** TypeScript doesn't know type after `isOk()`.

```typescript
// BAD: Reassigning breaks narrowing
let result = await getUser(id);
if (result.isOk()) {
  result = await updateUser(result.value);  // Breaks!
}

// GOOD: Separate variables
const getResult = await getUser(id);
if (getResult.isErr()) return getResult;
const updateResult = await updateUser(getResult.value);
```

### Error Type Lost

**Use `_tag` for narrowing:**
```typescript
if (result.isErr()) {
  switch (result.error._tag) {
    case "ValidationError":
      console.log(result.error.details);
      break;
    case "NotFoundError":
      console.log(result.error.resourceId);
      break;
  }
}
```

## MCP Issues

### Tool Not Appearing

1. Register before `start()`:
   ```typescript
   server.registerTool(myTool);
   server.start();  // After registration!
   ```

2. Check schema is valid Zod with `.describe()`:
   ```typescript
   const schema = z.object({
     query: z.string().describe("Required for AI"),
   });
   ```

### Tool Invocation Failing

1. Verify handler is async:
   ```typescript
   handler: async (input) => {  // Not sync!
     return Result.ok(data);
   }
   ```

2. Return Result, not raw value:
   ```typescript
   // BAD
   return { data: "value" };

   // GOOD
   return Result.ok({ data: "value" });
   ```

## CLI Output Issues

### JSON Not Printing

1. Force mode:
   ```typescript
   await output(data, { mode: "json" });
   ```

2. Check environment:
   ```bash
   OUTFITTER_JSON=1 myapp list
   OUTFITTER_JSON=0 myapp list --json  # Forces human!
   ```

3. Await output:
   ```typescript
   // BAD
   output(data);
   process.exit(0);  // May exit before output!

   // GOOD
   await output(data);
   ```

### Wrong Exit Code

1. Use `exitWithError`:
   ```typescript
   // BAD
   process.exit(1);

   // GOOD
   exitWithError(result.error);
   ```

2. Exit code table:

   | Category | Exit |
   |----------|------|
   | validation | 1 |
   | not_found | 2 |
   | conflict | 3 |
   | permission | 4 |
   | timeout | 5 |
   | rate_limit | 6 |
   | network | 7 |
   | internal | 8 |
   | auth | 9 |
   | cancelled | 130 |

## Logging Issues

### Redaction Not Working

```typescript
const logger = createLogger({
  redaction: { enabled: true },  // Must be true!
});

// Custom patterns
const logger = createLogger({
  redaction: {
    enabled: true,
    patterns: ["password", "apiKey", "myCustomSecret"],
  },
});
```

### Missing Context

```typescript
import { createChildLogger } from "@outfitter/logging";

const requestLogger = createChildLogger(ctx.logger, {
  requestId: ctx.requestId,
  handler: "myHandler",
});

requestLogger.info("Processing", { data });  // Includes requestId
```

### Wrong Level

```typescript
const logger = createLogger({
  level: process.env.LOG_LEVEL || "info",
});

// Hierarchy: trace < debug < info < warn < error < fatal
// "info" hides trace and debug
```

## Debugging Tools

### Trace Result Chain

```typescript
function traceResult<T, E>(name: string, result: Result<T, E>): Result<T, E> {
  console.log(`[${name}]`, result.isOk() ? "OK:" : "ERR:",
    result.isOk() ? result.value : result.error);
  return result;
}

const result = traceResult("getUser", await getUser(id));
```

### Inspect Context

```typescript
console.log("Context:", {
  requestId: ctx.requestId,
  hasLogger: !!ctx.logger,
  hasConfig: !!ctx.config,
  hasSignal: !!ctx.signal,
  cwd: ctx.cwd,
});
```

### Validate Zod Schema

```typescript
const parseResult = schema.safeParse(rawInput);
if (!parseResult.success) {
  console.log("Zod errors:", parseResult.error.issues);
}
```

## Related Skills

- `outfitter-stack:stack-patterns` — Correct patterns
- `outfitter-stack:stack-review` — Systematic audit

Overview

This skill troubleshoots Outfitter Stack issues across Result handling, MCP (tool) problems, CLI output and exit codes, and logging. It focuses on concrete checks and fixes to quickly surface root causes and restore expected behavior. Use it when Result values are wrong, tools don't register or invoke, CLI output is missing or exit codes are incorrect, or logging/redaction behaves unexpectedly.

How this skill works

The skill inspects common failure points and suggests code-level corrections: verifying Result usage and async/await, preserving TypeScript narrowing, and using error tags for precise handling. It checks MCP registration, Zod schema descriptions, and async handlers that must return Result objects. For CLI it enforces awaiting output, forcing JSON mode, and mapping errors to standard exit codes. For logging it validates redaction settings, context propagation, and log level configuration.

When to use it

  • You see Result values consistently return err when ok was expected
  • A registered MCP tool is missing from the server or never invoked
  • CLI commands produce no JSON or exit with the wrong code
  • Sensitive fields are not being redacted or logs lack request context
  • TypeScript narrowing stops working after result reassignment

Best practices

  • Always await Promises that return Result<T, E> to avoid returning Promises instead of Results
  • Avoid reassigning a Result variable after an isOk/isErr check; use separate variables to preserve narrowing
  • Use error discriminators (_tag) to switch on specific error shapes for safe handling
  • Register MCP tools before calling server.start() and validate Zod schemas with .describe()
  • Await output(...) before calling process.exit and use exitWithError for proper exit codes
  • Enable logger redaction and use createChildLogger to propagate request context

Example use cases

  • A command prints nothing because output(data) was not awaited and process exited early
  • An MCP tool's handler never runs because it was registered after server.start()
  • Validation errors are losing details; inspect validateInput usage and Zod safeParse results
  • Exit codes are generic; map Result error categories to the documented exit table
  • Secrets like passwords remain in logs; enable redaction and add custom patterns

FAQ

Why does TypeScript lose type narrowing after checking isOk()?

Reassigning the same variable after the isOk() check invalidates narrowing. Use separate const variables for each step so the compiler can track types.

My MCP tool is registered but not visible to the AI, what do I check?

Ensure registration occurs before server.start(), the handler is async, and the schema is a Zod object with .describe() on required fields.