Skip to content

Add isLoop helper to functions:init scaffolded handler#80

Merged
etbyrd merged 3 commits into
mainfrom
functions-init-loop-protection
May 12, 2026
Merged

Add isLoop helper to functions:init scaffolded handler#80
etbyrd merged 3 commits into
mainfrom
functions-init-loop-protection

Conversation

@etbyrd
Copy link
Copy Markdown
Member

@etbyrd etbyrd commented May 12, 2026

Summary

A deployed Primitive Function receives catch-all inbound for the managed *.primitive.email subdomain. That stream includes bounces and auto-replies generated by the handler's own outbound traffic. Without a loop guard the handler can respond to its own bounces and create a fan-out loop.

The scaffolded handler now exports a small isLoop(event) helper. The fetch dispatcher calls it right after the event-type check and returns early with Response.json({ skipped: 'loop' }) when it returns true.

Default predicate:

  • From on any *.primitive.email address (covers the catch-all managed subdomain, simple self-reply, and bounces from mailer-daemon@*.primitive.email).
  • From matching the configured REPLY_FROM (covers handlers whose sender is on a non-managed domain).

Comparisons are case-insensitive substring matches so a display-name form (Alice <alice@example.com>) works without parsing the bracketed address.

The default is intentionally small. The comment block above the helper lists common extensions so the next author knows where to add sophistication:

  • Match the org's signup / account-owner email.
  • Honor RFC 3834 Auto-Submitted headers and Precedence: bulk / List-Unsubscribe.
  • Track Message-ID / In-Reply-To chains to break ping-pong loops between two cooperating handlers on different domains.

Test plan

  • pnpm --filter @primitivedotdev/cli lint
  • pnpm --filter @primitivedotdev/cli typecheck
  • pnpm --filter @primitivedotdev/cli test functions-init (27 tests pass; new tests cover the helper export, the dispatcher call site, the two predicate arms, and the extension-point comment)

A deployed Function receives catch-all inbound for the managed
*.primitive.email subdomain, including bounces from its own outbound
traffic. Without a loop guard the handler can respond to its own
bounces and create a fan-out loop.

The scaffolded handler now exports a small isLoop(event) helper that
returns true when the From header is on any *.primitive.email address
or matches the configured REPLY_FROM. The fetch dispatcher calls it
right after the event-type check and returns early with skipped: loop.

The default is intentionally small. Comments above the helper list
common extensions (RFC 3834 Auto-Submitted headers, Message-ID chain
tracking, signup-email matching) so the next author knows where to add
sophistication without rediscovering the need.
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 12, 2026

Greptile Summary

This PR replaces the scaffolded handler's inline self-reply guard with a named, exported isLoop(event) helper and updates the fetch dispatcher to call it after the event-type check, returning early with { skipped: 'loop' } on a match.

  • The helper's two predicate arms — .primitive.email suffix check and case-insensitive REPLY_FROM substring check — are well-documented, including explicit acknowledgment of the substring false-positive trade-off and a comment block enumerating extension points (RFC 3834, Message-ID chains, signup-email matching).
  • Four new tests cover the helper export, dispatcher call site, both predicate arms, and the extension-point comment; the previous single test is cleanly replaced.

Confidence Score: 5/5

Safe to merge — the change is additive, scoped entirely to the scaffolded handler template, and ships with full test coverage.

The predicate logic is straightforward and its trade-offs (substring false positives, empty-From passthrough) are explicitly documented in both the comment block and the PR description, leaving nothing implicit for the next author. The dispatcher integration is minimal and the existing catch-all error handler remains intact.

No files require special attention.

Important Files Changed

Filename Overview
cli-node/src/oclif/commands/functions-init.ts Adds isLoop helper and dispatcher call site to the scaffolded handler template; refactors the previous single-line self-reply guard into a documented, extensible predicate with two arms (.primitive.email suffix and case-insensitive REPLY_FROM substring).
cli-node/tests/oclif/functions-init.test.ts Replaces the single self-reply test with four targeted assertions covering: helper export + dispatcher call, both predicate arms with .toLowerCase() and .includes(), and the extension-point comment block; all structured to test the rendered template string.

Reviews (3): Last reviewed commit: "Merge branch 'main' into functions-init-..." | Re-trigger Greptile

Comment thread cli-node/src/oclif/commands/functions-init.ts Outdated
@etbyrd etbyrd merged commit ffe9a2f into main May 12, 2026
9 checks passed
@etbyrd etbyrd deleted the functions-init-loop-protection branch May 12, 2026 17:33
@etbyrd etbyrd mentioned this pull request May 12, 2026
3 tasks
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.

1 participant