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-debugReview the files below or copy the command above to add this skill to your agents.
---
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
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.
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.
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.