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.
Scanned 9/6/2026
Install to Claude Code
npx -y skills add oimiragieo/agent-studio --skill sharp-edges --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Sharp Edges?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/oimiragieo-sharp-edges)More formats (shields.io, HTML) on the badges page.
---
name: sharp-edges
description: '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.'
version: 1.0.0
verified: true
lastVerifiedAt: '2026-02-28'
category: 'Debugging'
agents: [developer, code-reviewer, qa, security-architect]
user_invocable: true
invoked_by: both
tools: [Read, Write, Bash]
tags: [debugging, hazards, sharp-edges, windows, prototype-pollution, regex, async, bugs]
best_practices:
- 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 `\`.
**Fix:**
```javascript
// ALWAYS normalize before regex matching
const rel = path.relative(root, filePath).replace(/\\/g, '/');
```
**Test assertion:**
```javascript
assert(normalizePath('a\\b\\c') === 'a/b/c');
assert(!normalizePath('a\\b').includes('\\'));
```
**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.
**Fix:**
```javascript
// Use safeParseJSON — strips __proto__, constructor, prototype keys
const { safeParseJSON } = require('.claude/lib/utils/safe-json.cjs');
const { success, data } = safeParseJSON(rawInput, {});
```
**Test assertion:**
```javascript
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:**
```javascript
// CORRECT
if (shouldBlock) process.exit(2); // Block tool execution
process.exit(0); // Allow tool execution
// WRONG
if (shouldBlock) process.exit(1); // Not a valid block code
process.exit(false); // Not valid
```
**Test assertion:**
```javascript
// Mock hook execution and verify exit code
const 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.
**Fix:**
```javascript
// Use Promise.allSettled to get partial results
const results = await Promise.allSettled(tasks);
const succeeded = results.filter(r => r.status === 'fulfilled').map(r => r.value);
const failed = results.filter(r => r.status === 'rejected').map(r => r.reason);
return { succeeded, failed, partial: failed.length > 0 };
```
**Test assertion:**
```javascript
const tasks = [resolvesWith('ok'), rejectsWith('error'), resolvesWith('ok2')];
const result = await safeAllSettled(tasks);
assert(result.succeeded.length === 2);
assert(result.failed.length === 1);
```
---
## SE-05: ReDoS in Glob-to-Regex
**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:**
```javascript
// Use anchored, non-backtracking patterns
// BAD: (.*)* -> exponential backtracking
// GOOD: [^/]* -> linear, no backtracking
function globToRegex(glob) {
const escaped = glob.replace(/[.+^${}()|[\]\\]/g, '\\$&');
return escaped
.replace(/\*\*/g, '___DOUBLE___')
.replace(/\*/g, '[^/]*') // Single * -> no path separators
.replace(/___DOUBLE___/g, '.*'); // ** -> any path
}
```
**Test assertion:**
```javascript
const regex = globToRegex('**/*.js');
const longString = 'a'.repeat(10000) + '.ts';
const start = Date.now();
regex.test(longString);
assert(Date.now() - start < 100); // Must complete in <100ms
```
---
## SE-06: DST Arithmetic Bugs
**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:**
```javascript
// WRONG: assumes 24h = 1 day
const tomorrow = new Date(date.getTime() + 86400000);
// CORRECT: use date arithmetic, not ms arithmetic
const tomorrow = new Date(date);
tomorrow.setDate(tomorrow.getDate() + 1);
```
**Test assertion:**
```javascript
// Test across a known DST boundary (e.g., March DST change)
const dst = new Date('2026-03-08T01:00:00'); // Day before US spring forward
const 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:**
```javascript
// WRONG: mutates during iteration
arr.forEach(item => {
if (condition) arr.push(newItem); // Skips items
});
// CORRECT: collect mutations, apply after
const toAdd = [];
arr.forEach(item => {
if (condition) toAdd.push(newItem);
});
arr.push(...toAdd);
// OR: work on a copy
[...arr].forEach(item => { ... });
```
**Test assertion:**
```javascript
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:
```javascript
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.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!