You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs(claude): consolidate CLAUDE.md rules from stale branches (#608)
* docs(claude): consolidate CLAUDE.md rules from stale branches
Fold the genuinely valuable guidance scattered across several now-stale
worktree branches (fix/promise-race-leak, chore/parallel-claude-rule,
docs/claude-fix-warnings-rule) into a single coherent pass on CLAUDE.md,
and pull in two rules the socket-repo-template gold standard already
carries but this repo was missing.
- Restructure the Promise.race guidance into a dedicated "### Promise.race
in loops" subsection with the template's Safe/Leaky/Fix triad — same
content, but the three-state framing is easier to apply at review time
than a single run-on bullet.
- Add null-prototype rule: `{ __proto__: null, ...rest }` is already the
idiom used throughout src/socket-sdk-class.ts and src/file-upload.ts
(config objects, internal state, return shapes); codify it so new
contributions follow suit and prototype pollution surface stays small.
- Add Linear-reference rule: keep code and PR history tool-agnostic.
Forward-looking — no violations currently in the repo.
Deliberately NOT pulled from template: the "NEVER use dynamic imports"
rule. SDK uses `await import('node:fs')` legitimately in two places
(socket-sdk-class.ts:1931, :3818) for lazy loading; a blanket ban would
be wrong here.
* docs(sdk): expand batchPackageStream Promise.race rationale
Replace the 6-line comment landed in #600 with the fuller narrative that
was sitting uncommitted on the fix/promise-race-leak worktree — concrete
walkthrough of the handler-stacking trap, what each piece of the
single-waiter machinery is responsible for, and why the snapshot-and-
clear dance in deliverStep/deliverError matters.
No behavioral change; comments only. The mechanical fix already shipped
in #600 (376a743); this captures the reasoning someone will want when
they look at this code six months from now.
Copy file name to clipboardExpand all lines: CLAUDE.md
+10-1Lines changed: 10 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -41,7 +41,16 @@ The umbrella rule: never run a git command that mutates state belonging to a pat
41
41
- 🚨 **NEVER use `npx`, `pnpm dlx`, or `yarn dlx`** — use `pnpm exec <package>` or `pnpm run <script>`. Add tools as pinned devDependencies first.
42
42
-**minimumReleaseAge**: NEVER add packages to `minimumReleaseAgeExclude` in CI. Locally, ASK before adding — the age threshold is a security control.
43
43
- File existence: ALWAYS `existsSync` from `node:fs`. NEVER `fs.access`, `fs.stat`-for-existence, or an async `fileExists` wrapper. Import form: `import { existsSync, promises as fs } from 'node:fs'`.
44
-
-`Promise.race` / `Promise.any`: NEVER pass a long-lived promise (interrupt signal, pool member) into a race inside a loop. Each call re-attaches `.then` handlers to every arm; handlers accumulate on surviving promises until they settle. For concurrency limiters, use a single-waiter "slot available" signal (resolved by each task's `.then`) instead of re-racing `executing[]`. See nodejs/node#17469 and `@watchable/unpromise`. Race with two fresh arms (e.g. one-shot `withTimeout`) is safe.
44
+
- Null-prototype objects: ALWAYS use `{ __proto__: null, ...rest }` for config, return, and internal-state objects. Prevents prototype pollution and accidental inheritance. See `src/socket-sdk-class.ts` and `src/file-upload.ts` for examples.
45
+
- Linear references: NEVER reference Linear issues (e.g. `SOC-123`, `ENG-456`, Linear URLs) in code, code comments, or PR titles/descriptions/review comments. Keep the codebase and PR history tool-agnostic — tracking lives in Linear.
46
+
47
+
### Promise.race in loops
48
+
49
+
**NEVER re-race the same pool of promises across loop iterations.** Each call to `Promise.race([A, B, ...])` attaches fresh `.then` handlers to every arm; a promise that survives N iterations accumulates N handler sets. See [nodejs/node#17469](https://github.com/nodejs/node/issues/17469) and `@watchable/unpromise`.
50
+
51
+
-**Safe**: `Promise.race([fresh1, fresh2])` where both arms are created per call (e.g. one-shot `withTimeout` wrappers).
52
+
-**Leaky**: `Promise.race(pool)` inside a loop where `pool` persists across iterations (the classic concurrency-limiter bug) — also applies to `Promise.any` and long-lived arms like interrupt signals.
53
+
-**Fix**: single-waiter "slot available" signal — each task's `.then` resolves a one-shot `promiseWithResolvers` that the loop awaits, then replaces. No persistent pool, nothing to stack.
0 commit comments