# Expire Team Invitations After 7 Days in a Next.js App from a Linear Issue

Label a Linear issue and an agent makes team invitations expire after 7 days in your Next.js app, with a Drizzle migration, tests, and a pull request.

[Run this on Rush](https://cloud.getrush.ai/run/add-invitation-expiry/nextjs/), in a Project that has your repository.

## What the agent does

The issue names the behaviour: invitations older than 7 days must be rejected when accepted, the team page should show them as expired, and the column arrives as a Drizzle migration. The run starts with the issue text and the repository cloned. In nextjs/saas-starter the invitations table in lib/db/schema.ts has id, teamId, email, role, invitedBy, invitedAt and a status that defaults to pending, and there is no expiry column anywhere: the only trace of the concept is the error string "Invalid or expired invitation." returned when an acceptance lookup misses.

The agent follows the schema-first path the repository already uses. It adds expiresAt to the invitations table in lib/db/schema.ts, runs the db:generate script so drizzle-kit writes a new migration SQL file into lib/db/migrations beside the existing 0000_soft_the_anarchist.sql, and checks the generated SQL into the pull request. On a fresh database the app applies it with the db:migrate script; existing pending invitations get a backfill or a conservative default the pull description explains.

The acceptance check lives in the signUp server action in app/(login)/actions.ts, which today matches an invitation by id, email and status pending, with no time condition. The agent adds the expiry comparison there, so an old invitation is rejected with the same invalid-or-expired error the codebase already returns, and the status update stays untouched. The team page, app/(dashboard)/dashboard/page.tsx backed by /api/team, lists members only and shows no invitations at all, so the expired display needs one small extension: the team query in lib/db/queries.ts selects pending invitations with their expiry, and the page renders each as active or expired by the date. That is the whole surface; nothing else in the flow changes.

The repository has no tests, no test script and no framework, so the agent adds the smallest setup that covers the behaviour: vitest with tests for the acceptance action covering an expired invitation rejected, a fresh one accepted, and a wrong-email one refused, plus a test that the team query flags old invitations as expired. Then it runs pnpm build and the new tests, and opens a pull request linking back to the Linear issue.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "claude",
  "input": "A Linear issue labeled agent says: \"Expire Team Invitations After 7 Days in a Next.js App. Invitations older than 7 days must be rejected when accepted, and the team page should show them as expired. Add the column with a Drizzle migration.\" Do the work in this repository, add tests for it, run pnpm build and the new tests, and open a pull request that links back to the issue.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    }
  ]
}
```

1. **Get an API key.** In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
2. **Send the request.** POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
3. **Read what came back.** Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

## What comes back

What comes back is a pull request on its own branch. The diff is compact: expiresAt on the invitations table in lib/db/schema.ts, one generated migration SQL file, the time check in the signUp action, the invitations selection in the team query, an expired badge beside each old invitation on the team page, and the new test files. The generated SQL is checked in, so the schema change is reviewable as SQL, not just as TypeScript.

The pull request's description says how existing rows were handled, which tests cover the three acceptance paths, and that pnpm build and the new tests passed. The trajectory keeps the drizzle-kit generate output, the migration SQL, and the test run, so a reviewer can read exactly what the agent ran and what answered.

Before merging, the reviewer checks that an invitation created eight days ago is refused at acceptance with the existing error, that a fresh one still works end to end, that the team page distinguishes expired from pending at a glance, and that the migration applies cleanly against a copy of the production database shape. No env values or secrets change.

## Run it on every Linear issue labeled agent

Set up the automation once in the console: a Linear trigger on linear.issue.labeled with the labels you choose, pointed at the Project whose repository the agent should work in. Labeling an issue starts a run with the issue as its context, and the agent opens a pull request that links back, so a triaged issue becomes reviewable code without a developer picking it up.

Changes made by an app integration never fire the trigger, so an agent's own pull requests cannot re-trigger it. The run works in the Project's repository with its secrets, including POSTGRES_URL for the migration and tests; keep the agent label for issues described well enough to act on, and the automation's prompt tells the agent to ask on the issue when it is not.

## Why run it on Rush?

The change is small but touches four layers at once, schema, migration, server action and UI, and a handoff between people at each layer is where the expiry check gets lost. On Rush one request carries the whole change through a sandbox with the repository and a database, generates the migration from the schema rather than writing SQL by hand, and comes back with tests already run.

The pull request is the review unit. It links the issue, the description names the acceptance paths covered, the migration SQL is in the diff, and the trajectory keeps pnpm build and the test run with their output. Because the trigger fires per label, every well-described issue in the backlog gets the same treatment: one run, one branch, one pull request held for a human merge.

## Frequently asked questions

### What happens to invitations sent before the migration?

The pull description states it explicitly: either a backfill sets expiresAt from invitedAt so old invitations expire on schedule, or pending ones without a date are treated conservatively. The reviewer picks the policy by reading the migration, and the tests cover whichever the agent chose.

### Where does the 7-day check live?

In the signUp server action, at the moment an invitation is accepted. The lookup already matches id, email and pending status; the agent adds the expiry comparison there, so an old invitation fails with the same "Invalid or expired invitation." error the app already returns for bad links.

### Does the team page change?

It gains an invitations section. Today it lists members only; the team query starts selecting pending invitations with their expiry and the page renders each as active or expired, which is the display the issue asked for and nothing more.

### The repo has no tests. What gets added?

The smallest setup that covers the behaviour: vitest with tests for the three acceptance paths, expired, fresh and wrong email, and a test that the team query flags old invitations as expired. The pull description lists the new files so the added tooling is reviewable on its own.

### Can I make the expiry window something other than 7 days?

Yes. Name the window in the issue and it lands in the same place, a schema column, one comparison in the acceptance action, and a badge on the team page. Keep the issue text as concrete as the example above and the pull request stays this small.
