Living catalogue of 7 known hazard entries (SE-01 through SE-07) specific to agent-studio: Windows backslash paths, prototype pollution, hook exit codes, async swallowing, ReDoS in glob-to-regex, DST arithmetic, array mutation during iteration. Each entry: symptom, root cause, fix, test assertion.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Living catalogue of 7 known hazard entries (SE-01 through SE-07) specific to agent-studio: Windows backslash paths, prototype pollution, hook exit codes, async swallowing, ReDoS in glob-to-regex, DST arithmetic, array mutation during iteration. Each entry: symptom, root cause, fix, test assertion.
["Invoke at the START of any debugging session","Match your symptom against SE-01 through SE-07 before exploring new theories","If your bug is new and reproducible, add it to this catalogue"]
error_handling
graceful
source
builtin
trust_score
100
provenance_sha
6f5d33597c87879f
Sharp Edges
Living catalogue of confirmed hazard patterns in agent-studio. Each entry documents a real bug we've shipped. Invoke this skill during debugging and code review to check against known failure modes.
SE-01: Windows Backslash Paths
Symptom: Glob patterns match correctly in CI (Linux) but silently fail on developer machines (Windows).
Root cause:path.relative() returns backslash-separated paths on Windows (node_modules\foo), but glob patterns use forward slashes. [^/]* in regex won't block \.
Files affected:.claude/lib/utils/path-constants.cjs, any glob-based exclusion logic.
SE-02: Prototype Pollution via JSON.parse
Symptom:Object.prototype gains unexpected properties after parsing malformed JSON from hook input or agent memory.
Root cause:JSON.parse('{"__proto__":{"isAdmin":true}}') silently mutates Object.prototype in older Node.js versions or when objects are created via Object.create(null) checks are missed.
const before = Object.getPrototypeOf({});
safeParseJSON('{"__proto__":{"evil":true}}', {});
assert(!{}.evil);
Files affected: Any hook that calls JSON.parse() on stdin input.
SE-03: Hook Exit Code Protocol
Symptom: Hook appears to "block" when it should allow, or silently "allows" when it should block.
Root cause: Hooks use exit codes 0 (allow) and 2 (block). Using process.exit(1) or process.exit(true) does not block — it produces unexpected behavior.
Fix:
// CORRECTif (shouldBlock) process.exit(2); // Block tool execution
process.exit(0); // Allow tool execution// WRONGif (shouldBlock) process.exit(1); // Not a valid block code
process.exit(false); // Not valid
Test assertion:
// Mock hook execution and verify exit codeconst result = execHook(input);
assert(result.exitCode === 0 || result.exitCode === 2);
Files affected: All hooks in .claude/hooks/.
SE-04: Async Exception Swallowing in Promise.all
Symptom: One failing async operation silently causes all results to be lost; agent believes all tasks succeeded.
Root cause:Promise.all([...]) rejects on the first failure and discards all other results. When wrapped in try/catch that returns [], the caller sees empty results with no error.
Symptom: Glob pattern matching hangs or takes exponential time on long path strings.
Root cause: Naive glob-to-regex conversion produces patterns with nested quantifiers like (.*)* or (.+)+ which backtrack exponentially on non-matching strings.
Fix:
// Use anchored, non-backtracking patterns// BAD: (.*)* -> exponential backtracking// GOOD: [^/]* -> linear, no backtrackingfunctionglobToRegex(glob) {
const escaped = glob.replace(/[.+^${}()|[\]\\]/g, '\\$&');
return escaped
.replace(/\*\*/g, '___DOUBLE___')
.replace(/\*/g, '[^/]*') // Single * -> no path separators
.replace(/___DOUBLE___/g, '.*'); // ** -> any path
}
Symptom: Date calculations are off by 1 hour for events that cross daylight saving time boundaries.
Root cause: Adding 24 60 60 * 1000ms to a timestamp does not always equal "tomorrow" — DST transitions can make a day 23 or 25 hours long.
Fix:
// WRONG: assumes 24h = 1 dayconst tomorrow = newDate(date.getTime() + 86400000);
// CORRECT: use date arithmetic, not ms arithmeticconst tomorrow = newDate(date);
tomorrow.setDate(tomorrow.getDate() + 1);
Test assertion:
// Test across a known DST boundary (e.g., March DST change)const dst = newDate('2026-03-08T01:00:00'); // Day before US spring forwardconst nextDay = addDays(dst, 1);
assert(nextDay.getDate() === 9);
assert(nextDay.getHours() === 1); // Same time, next day
SE-07: Array Mutation During forEach Iteration
Symptom: Items are skipped or processed twice; behavior is non-deterministic.
Root cause: Mutating an array (push, splice, shift) while iterating with forEach or for...of causes the iterator to skip or re-visit elements.
Fix:
// WRONG: mutates during iteration
arr.forEach(item => {
if (condition) arr.push(newItem); // Skips items
});
// CORRECT: collect mutations, apply afterconst toAdd = [];
arr.forEach(item => {
if (condition) toAdd.push(newItem);
});
arr.push(...toAdd);
// OR: work on a copy
[...arr].forEach(item => { ... });
Test assertion:
const arr = [1, 2, 3];
const result = safeForEach(arr, item => item * 2);
assert(result.length === 3); // Original not mutated, all items processed
Usage
Invoke at the START of any debugging session:
Skill({ skill: 'sharp-edges' });
Then match your symptom against SE-01 through SE-07. If your bug is new and reproducible, add it to this catalogue.
Adding New Entries
Pattern for new entries:
## SE-0N: [Short Title]
**Symptom:** [What you observe]
**Root cause:** [Why it happens]
**Fix:** [Code fix with before/after]
**Test assertion:** [Minimal test that would catch it]
**Files affected:** [Where in agent-studio this applies]
Memory Protocol
After invoking: if you find a new sharp edge, append it here via technical-writer agent.
Memory Protocol (MANDATORY)
Before starting:
Read .claude/context/memory/learnings.md
After completing:
New pattern -> .claude/context/memory/learnings.md
Issue found -> .claude/context/memory/issues.md
Decision made -> .claude/context/memory/decisions.md
ASSUME INTERRUPTION: If it's not in memory, it didn't happen.