Skip to content
Development
Skill

/remix-v2-meta-sessions-review

Reviews Remix v2 code for v1-shape meta exports (BREAKING in v2), cookie security gaps (httpOnly, secure, secrets rotation), auth gates in wrong layer, and missing CSRF. Use when reviewing meta/SEO, session, auth, or form-mutation code in a Remix v2 codebase.

From plugin
beagle
85139 skills2 commands

Claude Code

$ npx -y skills add existential-birds/beagle --skill remix-v2-meta-sessions-review --agent claude-code

SKILL.md

remix-v2-meta-sessions-review.SKILL.md
name: remix-v2-meta-sessions-review
description: Reviews Remix v2 code for v1-shape meta exports (BREAKING in v2), cookie security gaps (httpOnly, secure, secrets rotation), auth gates in wrong layer, and missing CSRF. Use when reviewing meta/SEO, session, auth, or form-mutation code in a Remix v2 codebase.
user-invocable: false

Remix v2 Meta, Sessions, Auth, and CSRF Code Review

Reviews Remix v2 meta/SEO, session, auth-gate, and CSRF code paths. Loaded by the umbrella `review-remix-v2` reviewer when a diff touches any of: `meta`/`links` exports, `root.tsx`, `*.server.ts` session/cookie modules, loaders/actions reading or writing `session`, or `<Form>`/`useFetcher` mutations.

See [remix-v2-meta-sessions](../remix-v2-meta-sessions/SKILL.md) for canonical patterns.

Quick Reference

| Issue Type | Reference | |------------|-----------| | **`meta` returning v1 object shape (BREAKING)**, OG shorthand, `document.title` in effect, missing `<Meta />`/`<Links />`, parent merge | [references/meta-v2-shape.md](references/meta-v2-shape.md) | | Missing `httpOnly`/`secure`, hardcoded secrets, single-string `secrets`, replace-not-prepend rotation | [references/cookie-security.md](references/cookie-security.md) | | Auth check in component, logout in loader, missing `commitSession`, `flash` without commit | [references/auth-gates.md](references/auth-gates.md) | | Manual `fetch` POST bypassing CSRF, token in session cookie, no CSRF protection, shared secrets | [references/csrf.md](references/csrf.md) |

**Highest-stakes detection — call out first:** v1 `meta` object shape (`return { title, description }`) in a v2 codebase. It typechecks, but the runtime ignores it and the page renders with **no title and no meta tags**. Grep every `export const meta` and confirm the return value starts with `[`, not `{`.

Review Checklist

  • [ ] `meta` returns `MetaDescriptor[]` (array starts with `[`), NOT the v1 object shape
  • [ ] OG / Twitter tags use `{ property, content }`, NOT v1 shorthand `{ "og:title": "..." }`
  • [ ] No `document.title = "..."` or `useEffect(() => { document.title = ... })` — meta is set via the `meta` export
  • [ ] `root.tsx` includes `<Meta />` and `<Links />` inside `<head>`
  • [ ] Child `meta` that wants parent values uses `matches.flatMap((m) => m.meta ?? [])`
  • [ ] `meta` null-guards `data` (loader may not have run / returned `undefined` on 404)
  • [ ] Cookie config sets `httpOnly: true` and `secure: process.env.NODE_ENV === "production"`
  • [ ] `secrets` is read from `process.env` (no hardcoded strings, no committed `.env.example` values)
  • [ ] `secrets` is an array supporting rotation (prepend new, keep old) — not a single value
  • [ ] Every `session.set`/`session.unset`/`session.flash` is followed by a response with `"Set-Cookie": await commitSession(session)`
  • [ ] Auth gate is in `loader` (or `action`) via `requireUserId(request)` — NOT a component-level redirect
  • [ ] Logout is an `action` (POST), not a `loader` (GET)
  • [ ] Mutating actions call `csrf.validate(request)` when CSRF protection is in use
  • [ ] CSRF token uses a dedicated `createCookie("csrf", ...)`, NOT the session cookie
  • [ ] Mutations use `<Form>` / `useFetcher` so `AuthenticityTokenInput` attaches the token (no manual `fetch` POST)

Valid Patterns (Do NOT Flag)

These are correct usage — do not report as issues:

  • **`sameSite: "lax"`** — acceptable default. Not every app needs `"strict"`; flag only when threat model warrants stricter (e.g. CSRF protection is otherwise absent).
  • **`meta` returning `[]`** — legitimate when the route intentionally emits no meta (inherits root tags or relies on a sibling).
  • **`links` returning `[]`** — legitimate when the route has no route-specific stylesheets or preloads.
  • **`session.flash(...)` followed on the next line by `commitSession(session)`** — the standard 2-line flash pattern. The separation is correct; do not flag it as "missing commit".
  • **Auth check in `action` (not `loader`)** — correct for POST-only routes (e.g. logout, delete). Loaders gate GETs; actions gate mutations.
  • **`charset` and `viewport` as plain JSX `<meta>`** in `root.tsx`'s `<head>` — preferred over the `meta` export to avoid duplicate-tag warnings under v2's no-merge behavior.
  • **`secrets: [process.env.X!, process.env.X_OLD!]`** — `!` non-null assertion is acceptable when a fail-fast guard above (`if (!process.env.X) throw`) is present.
  • **`throw redirect(...)`** inside a loader/action — canonical Remix pattern; the thrown response is intentional.
  • **`commitSession` called in a loader (not just an action)** — required when a loader reads a flash message and must clear it.

Context-Sensitive Rules

Only flag these issues when the specific context applies:

| Issue | Flag ONLY IF | |-------|--------------| | Missing CSRF validation in action | App declares `remix-utils/csrf` as its protection mechanism, OR the action is public-facing (not internal/VPN-gated) AND no `Origin` check is present | | `sameSite: "lax"` | App has no library-based CSRF protection AND no `Origin` check — `"lax"` then becomes the only defense and is insufficient | | Missing `secure` flag | Cookie config is the production session/CSRF cookie (not a test fixture or commented example) | | `meta` returning `[]` | The route is documented as needing route-specific tags (e.g. a public landing page) — empty is usually intentional inheritance, do not flag by default | | Auth check in `action` not `loader` | Route is GET-renderable (has a `loader`) — for POST-only routes, `action` is the correct gate | | Logout in `action` AND `<Form method="post">` | Never flag — that is the canonical pattern | | Manual `fetch` POST | The target is an internal Remix action AND no CSRF token is attached via headers | | `secrets: [singleValue]` | App is in production OR has been deployed for long enough to need rotation — flag as recommendation, not CRITICAL |

Hard gates (before writing findings)

Run these in order. **Do not draft

Read more
@existential-birds
Ships withbeagle

Image: NASA, Public Domain. Source Beagle is an Agent Skills marketplace: framework-aware code review, documentation, testing, architectural analysis, and git workflows for any compatible coding agent.

Stars on GitHub

Momentum

stars in total
85
Star history of existential-birds/beagle on GitHub, a chart by star-history.com

Other skills on beagle.

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills10d309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc12d293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react1d252k
@facebook@facebookInstall Skill

Questions about remix-v2-meta-sessions-review.

Which coding agents can use remix-v2-meta-sessions-review?
remix-v2-meta-sessions-review installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.