The CLAUDE.md ### Lint rules section is the headline; this file is the full rationale and the cascade behavior.
Fleet lint rules are guardrails for AI-generated code. Make them strict:
- Errors, not warnings. A warning is silently ignored; an error blocks the commit. Severity
"warn"belongs to user-facing tools (browser dev consoles, ad-hoc scripts), not the fleet's CI gate. Default to"error"for new rules; bump existing"warn"entries to"error"when you touch them. - Fixable when possible. Every new rule that can express a deterministic rewrite should ship an autofix. The
fixable: 'code'meta flag plus afix(fixer) => ...incontext.reportletspnpm exec oxlint --fixclean up the violation. Reporting-only rules are fine when the fix requires human judgment (e.g., picking betweenhttpJsonvshttpTextto replacefetch()); say so explicitly in the rule docstring. - Skill or hook ≠ no rule. If a behavior already lives as a skill (the canonical write-up) or a hook (PreToolUse blocking), still encode the lint rule on top — defense in depth. The skill is documentation, the hook is edit-time enforcement, the lint rule is commit-time enforcement.
- Tooling: oxlint + oxfmt only. No ESLint, no Prettier. The fleet socket-* oxlint plugin lives in
template/.config/oxlint-plugin/; new fleet rules land there. Wire via.oxlintrc.jsonjsPluginsand thesocket/namespace.
When introducing a new rule fleet-wide, expect it to surface dozens of pre-existing violations. That's the rule earning its keep, not noise — surface the cleanup as a separate task rather than auto-fixing in the same PR.
oxlint-disable-next-line <rule> -- <reason> is correct when a single call site has a genuine, code-local justification that wouldn't apply to siblings. Stacking the same comment on adjacent lines is the failure mode.
Wrong — three byte-identical disables on consecutive lines:
// oxlint-disable-next-line socket/prefer-exists-sync -- isDir is the unit under test.
expect(await isDir(dir)).toBe(true)
// oxlint-disable-next-line socket/prefer-exists-sync -- isDir is the unit under test.
expect(await isDir(file)).toBe(false)
// oxlint-disable-next-line socket/prefer-exists-sync -- isDir is the unit under test.
expect(await isDir(other)).toBe(false)Right (helper pattern) — lift the rule-violating call behind a one-line helper. The helper's declaration carries the disable once; the test reads clean:
it('isDir returns true for directories', async () => {
// oxlint-disable-next-line socket/prefer-exists-sync -- isDir is the unit under test.
const callIsDir = (p: string) => isDir(p)
expect(await callIsDir(dir)).toBe(true)
expect(await callIsDir(file)).toBe(false)
expect(await callIsDir(other)).toBe(false)
})Right (sentinel-constant pattern) — when the violation is a literal value rather than a call (e.g., GraphQL spec mandates null for unresolved nodes), name the literal at module scope:
// oxlint-disable-next-line socket/prefer-undefined-over-null -- GraphQL spec returns null for unresolved nodes.
const GRAPHQL_NULL = null
// Then in tests:
JSONStringify({
data: { repository: { tagRef: GRAPHQL_NULL, branchRef: GRAPHQL_NULL } },
})Why this matters: stacked identical disables are visual noise that obscures the real signal (per-line disables exist to highlight exceptional code). When the disable repeats verbatim, the exception isn't per-line — it's per-pattern, and the pattern deserves its own name.
When per-call-site IS correct: the reasons genuinely differ, OR the disables sit on lines that aren't adjacent. Two disables 20 lines apart in the same file with the same rule + same reason is fine; what's banned is the consecutive stack on adjacent lines.