Skip to content

feat(compiler): SYN010 — warn on setTimeout/setInterval/queueMicrotask calls that defer side effects (?bs 0.7+)#151

Merged
marcelofarias merged 4 commits into
mainfrom
botkowski/syn010-timer-bypass
Jun 12, 2026
Merged

feat(compiler): SYN010 — warn on setTimeout/setInterval/queueMicrotask calls that defer side effects (?bs 0.7+)#151
marcelofarias merged 4 commits into
mainfrom
botkowski/syn010-timer-bypass

Conversation

@marcelofarias

Copy link
Copy Markdown
Owner

Summary

  • Adds SYN010: a new warning that fires when a fn body calls setTimeout(...), setInterval(...), or queueMicrotask(...) at ?bs 0.7+
  • These globals schedule callbacks to run after the fn returns — any effects inside those callbacks are invisible to callers: no capability declaration, no writes {} label, and no throws {} entry can cover them
  • Same bypass class as fetch (SYN007) and WebSocket (SYN008): real side effects that sidestep the declared capability surface, but deferred rather than immediate
  • Suppressed inside unsafe "reason" { } blocks and unsafe "reason" fn bodies
  • Note: should be merged after PR feat(compiler): SYN009 — warn on XMLHttpRequest() calls that bypass the net capability model (?bs 0.7+) #150 (SYN009) lands; SYN010 is the next available code in the bypass series

Changes

File Change
packages/compiler/src/error-codes.ts Add SYN010 entry (rule, idiom, rewrite, example)
packages/compiler/src/passes/syn-check.ts Add SYN010 detection; add SYN006 + SYN010 to module header comment; fix outdated unsafe-fn skip comment
packages/compiler/tests/syn010-check.test.ts 12 tests: setTimeout/setInterval/queueMicrotask, optional-call form, severity, version gating, unsafe suppression, member-call exclusion, bare reference, multi-call count
packages/compiler/tests/error-codes.test.ts Add SYN010 to exhaustive allowlist
packages/mcp/src/explanations.ts Add SYN010 long-form explanation with fails/passes examples
packages/mcp/tests/server.test.ts Add SYN010 to KNOWN_CODES list
AGENTS.md Add SYN010 row to diagnostic table
README.md Add SYN010 to explain tool code list

Test plan

  • pnpm -r build && pnpm test — 1161 tests pass
  • SYN010 fires on setTimeout(), setInterval(), queueMicrotask()
  • SYN010 fires on optional-call form setTimeout?.()
  • SYN010 suppressed inside unsafe {} blocks and unsafe fn bodies
  • SYN010 does not fire on obj.setTimeout() (member call on a local)
  • SYN010 does not fire on bare setTimeout reference (not called)
  • SYN010 does not fire below ?bs 0.7
  • Multiple timer calls in one fn produce multiple diagnostics

🤖 Generated with Claude Code

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new compiler warning (SYN010) to flag setTimeout(...), setInterval(...), and queueMicrotask(...) calls in function bodies at ?bs 0.7+, since deferred callbacks can perform side effects outside the caller-visible capability surface. The PR also wires the new code through the error-code registry, MCP explain output, docs, and test suites.

Changes:

  • Implement SYN010 detection in syn-check with unsafe {} / unsafe fn suppression and member-call exclusion.
  • Register SYN010 in the compiler error-code registry and MCP explanations, and expose it via docs/tooling lists.
  • Add a dedicated SYN010 test suite and update “known codes”/allowlists across packages.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
README.md Adds SYN010 to the MCP explain tool’s supported diagnostic code list.
packages/mcp/tests/server.test.ts Adds SYN010 to the MCP server test’s KNOWN_CODES list.
packages/mcp/src/explanations.ts Adds a long-form SYN010 explanation plus fails/passes examples.
packages/compiler/tests/syn010-check.test.ts Introduces SYN010 behavior tests (version gating, unsafe suppression, member-call exclusion, etc.).
packages/compiler/tests/error-codes.test.ts Adds SYN010 to the exhaustive error-code allowlist.
packages/compiler/src/passes/syn-check.ts Implements SYN010 token-based detection and updates module header comments.
packages/compiler/src/error-codes.ts Registers SYN010 (rule/idiom/rewrite/example) in the compiler’s error-code registry.
AGENTS.md Adds SYN010 to the diagnostic table for agent/tooling reference.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/compiler/tests/syn010-check.test.ts
Comment thread packages/compiler/src/error-codes.ts
Comment thread packages/compiler/src/error-codes.ts

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Comment thread packages/compiler/src/passes/syn-check.ts Outdated
Comment thread packages/compiler/src/error-codes.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Comment thread packages/compiler/src/passes/syn-check.ts
Comment thread packages/mcp/src/explanations.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Comment thread packages/compiler/src/passes/syn-check.ts Outdated
Comment thread packages/compiler/tests/syn010-check.test.ts
marcelofarias pushed a commit that referenced this pull request Jun 11, 2026
…he module capability model (?bs 0.7+)

- Adds SYN011: fires when a fn body calls import(specifier) — the dynamic import form
- Dynamic imports load a module at runtime whose capability surface is unbounded:
  CAP001 cannot see what the dynamically loaded module does; the fn header cannot
  declare capabilities it doesn't know about at compile time
- import.meta (followed by `.`) is excluded — it's a property, not a call
- Suppressed inside `unsafe "reason" { }` blocks and `unsafe "reason" fn` bodies
- 10 tests: bare/template-literal/awaited forms, unsafe suppression, import.meta
  exclusion, version gate, severity, multi-call count, bare reference non-fire
- Should be merged after SYN010 (PR #151) lands — SYN011 is the next available code

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@marcelofarias marcelofarias requested a review from Copilot June 11, 2026 22:47

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Comment thread packages/compiler/tests/syn010-check.test.ts
Comment thread packages/compiler/tests/syn010-check.test.ts
Comment thread AGENTS.md
Marcelo Farias and others added 4 commits June 11, 2026 23:34
…k calls in fn bodies (?bs 0.7+)

- Adds **SYN010**: a new warning that fires when a fn body calls `setTimeout(...)`,
  `setInterval(...)`, or `queueMicrotask(...)` at `?bs 0.7+`
- These globals schedule callbacks that run after the fn returns — any effects inside
  those callbacks are invisible to callers: no capability declaration, no `writes {}`
  label, and no `throws {}` entry can cover them
- Same bypass class as `fetch` (SYN007) and `WebSocket` (SYN008): real side effects
  that sidestep the declared capability surface, but deferred rather than immediate
- Suppressed inside `unsafe "reason" { }` blocks and `unsafe "reason" fn` bodies
- Member calls (`obj.setTimeout(...)`) and bare references (without `(`) are excluded
- Note: should be merged after PR #150 (SYN009) lands; SYN010 is the next available code

Changes:
- error-codes.ts: add SYN010 entry with rule/idiom/rewrite/example
- syn-check.ts: add SYN010 detection; add SYN006 + SYN010 to module header comment;
  fix outdated unsafe-fn skip comment
- tests/syn010-check.test.ts: 12 tests covering all three globals, optional-call form,
  severity, version gating, unsafe suppression, member-call exclusion, bare reference
- tests/error-codes.test.ts: add SYN010 to exhaustive allowlist
- mcp/src/explanations.ts: add SYN010 long-form explanation with fails/passes examples
- mcp/tests/server.test.ts: add SYN010 to KNOWN_CODES list
- AGENTS.md: add SYN010 row to diagnostic table
- README.md: add SYN010 to explain tool code list
…name and reason string

- Hoist TIMER_GLOBALS to module scope (was re-created per fn, wasteful in hot pass)
- Rename `fn` parameter to `callback` in unsafe-fn test (fn is a reserved keyword)
- Fix unsafe reason string: "suspends execution" → "schedules deferred effect" (consistent with idiom)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…eclarations and explanation codes

- syn-check: exclude function declarations named setTimeout/setInterval/queueMicrotask
  (prev significant token is `function`) — these are definitions, not calls
- syn-check: exclude object/class method shorthands where after closing `)` is `{` or `:`
- tests: add two tests covering function-declaration and object-method-shorthand exclusions
- explanations: remove SYN007/SYN008 code references in SYN010 body; reference by name instead
  to avoid dangling code cross-references until those PRs land

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…yword declarations

- syn-check.ts: also skip `fn setTimeout(...) -> void {}` botscript-style nested
  function declarations (prev token is `keyword`/`fn`), not just JS `function`
  declarations — prevents false positives on nested fn declarations inside fn bodies
- syn010-check.test.ts: add test asserting SYN010 does NOT fire on
  `fn setTimeout(delay: number) -> void { }` inside a fn body

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@marcelofarias marcelofarias force-pushed the botkowski/syn010-timer-bypass branch from 9234bd2 to 6847365 Compare June 12, 2026 02:35
@marcelofarias marcelofarias merged commit d2537cf into main Jun 12, 2026
2 checks passed
marcelofarias pushed a commit that referenced this pull request Jun 12, 2026
…he module capability model (?bs 0.7+)

- Adds SYN011: fires when a fn body calls import(specifier) — the dynamic import form
- Dynamic imports load a module at runtime whose capability surface is unbounded:
  CAP001 cannot see what the dynamically loaded module does; the fn header cannot
  declare capabilities it doesn't know about at compile time
- import.meta (followed by `.`) is excluded — it's a property, not a call
- Suppressed inside `unsafe "reason" { }` blocks and `unsafe "reason" fn` bodies
- 10 tests: bare/template-literal/awaited forms, unsafe suppression, import.meta
  exclusion, version gate, severity, multi-call count, bare reference non-fire
- Should be merged after SYN010 (PR #151) lands — SYN011 is the next available code

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants