What the agent does
The agent clones the repository into its own sandbox and maps how Next.js and the monorepo reach code before it trusts any tool's output. In a Next.js codebase like dubinc/dub, the root package.json sets packageManager to pnpm@9.15.9 and runs Turborepo 1 (turbo.json still uses the pipeline key), and pnpm-workspace.yaml covers apps/*, packages/*, packages/embeds/* and apps/web/.react-email. The app is apps/web on Next.js 15.5.8, with Prisma 6.19.1 reading a schema split across files in apps/web/prisma/schema, so build there is pnpm prisma:generate && next build.
In a Next.js app most entry points are files no one imports: every page.tsx, layout.tsx, route.ts, not-found.tsx and opengraph-image.tsx under apps/web/app is loaded by the framework, as are middleware.ts, instrumentation.ts and next.config.js. The agent runs knip with its Next.js plugin enabled for apps/web and adds the entries this repository needs on top. scripts/run.ts starts tsx ./scripts/${command}.ts from a name on the command line, so every file under apps/web/scripts looks unused to static analysis and is marked as an entry. vitest.config.ts loads tests/setupTests.ts, the @/lib/*, @/ui/* and @/scripts/* aliases come from apps/web/tsconfig.json, and packages/email exports ./templates/* as a wildcard, so each of its templates is reachable by name.
Published packages need their own rule. The root scripts publish @dub/ui, @dub/utils, @dub/embed-core, @dub/embed-react, @dub/tailwind-config and the CLI to npm, so an export of one of them that nothing in the monorepo imports may still be used by someone outside it. The agent lists those as public API to decide on, never as dead. It also flags configuration that points at nothing: apps/web/tsconfig.json includes ../../packages/blocks/src/event-list.tsx, and there is no packages/blocks in the repository.
Each remaining candidate is then checked by removing it on a scratch branch and running what would catch the breakage: tsc --noEmit for apps/web and the check-types script in packages/ui and packages/utils, pnpm build in apps/web (Prisma generate and next build), pnpm build:packages for the packages, and next lint. The Vitest suite in apps/web/tests calls a running deployment through E2E_BASE_URL (tests/utils/env.ts), so the agent says plainly that it could not run it in the sandbox rather than reporting it as passed. A dependency is checked by removing it from its workspace's package.json, running pnpm install and the same commands. Anything that fails goes back, with its error, into a list of things that looked dead and were not.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "Clone https://github.com/dubinc/dub. Find unused files, exports and package dependencies with knip, configured for this pnpm and Turborepo monorepo: enable the Next.js plugin for apps/web, treat apps/web/scripts/**, tests/setupTests.ts and packages/email/src/templates/* as entries, and resolve the aliases in apps/web/tsconfig.json. List exports of packages published to npm (@dub/ui, @dub/utils, the embeds, the CLI) separately as public API. Check each other candidate by removing it on a scratch branch and running tsc --noEmit in apps/web, pnpm check-types in packages/ui and packages/utils, pnpm build in apps/web, pnpm build:packages and next lint. Say which checks you could not run and why. Write dead-code.md listing only what passed, grouped into files, exports and dependencies, each with the command output, plus the candidates that failed with their errors and any config that points at missing paths. Push nothing and open no pull request."
}- 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.
- 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.
- 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 dead-code.md, a session artifact with a download link that lasts an hour. It lists what can be removed in three groups, files, exports and dependencies, and for each line names the workspace and the checks that still passed with it gone. A separate section lists exports of the published packages that only outside users could reach, and another lists configuration that refers to paths that no longer exist, such as a tsconfig.json include.
The report is as honest about what it could not prove as about what it did. The checks section says which commands ran on every candidate and which could not run in the sandbox, such as an integration suite that needs a live deployment, so a reader knows exactly what each removal rests on. The session's transcript keeps every knip run and every remove-and-rebuild cycle with its output.
Before deleting, a reviewer looks for code reached in ways neither knip nor the build can see: a helper only a cron route listed in apps/web/vercel.json reaches through a computed import, a template picked by name at runtime, or a script someone runs by hand. Route handlers themselves are entries and never reported, so a cron that is no longer needed is a question for the team, not this report. Then a follow-up message to the same session can ask for a pull request that removes the confirmed items, one group per commit.
Run it on every release branch cut
Dead code is cheapest to remove right after a release, when nothing is in flight. When your release process cuts a branch, have it send this request from your server with an Idempotency-Key built from the release tag, so each release starts one run.
A webhook URL on the API key gets run.completed with the session_url when the run finishes. Your server reads the session, takes the dead-code.md link from its artifacts and opens a cleanup issue with it attached.
The use case this job follows, with its automation and trigger: Produce a report or file, no repo.
Why run it on Rush?
Proving removals in a monorepo this size means many cycles of edit, install, Prisma generate, type check and next build, each one long enough to block a laptop. In a Rush sandbox all of those run on the agent's own machine and disk, with its own node_modules and Prisma client, and your working tree and branches are never touched.
A report that says something is unused has to be believed by people who did not run it. This one carries the transcript of every command and its output, and says outright which checks could not run, so each line can be traced to a passing build. Several repositories, or several workspaces of one, can be checked at once, one run each, up to the key's concurrent run limit.
Frequently asked questions
Does it understand the App Router's file conventions?
Yes. Pages, layouts, route handlers, not-found, Open Graph image files and middleware are loaded by Next.js rather than imported, so the agent treats them as entry points through knip's Next.js plugin. A file under app/ is only reported if it is not one of those conventions and nothing imports it.
What about tests that need secrets or a live deployment?
The agent runs every check that works in a clean sandbox and lists the ones it could not, with the reason. For an integration suite like the one that reads E2E_BASE_URL, you can save the values as Project secrets and start the session in that Project, or run that suite yourself before deleting.
Will it delete code from my repository?
No. Removals happen on a scratch branch in the sandbox and exist only to run the checks. The prompt says to push nothing, and the output is a report. Ask for a pull request in a follow-up message once you have read it.
Does it work with pnpm workspaces and Turborepo?
Yes. The agent reads pnpm-workspace.yaml and turbo.json, configures knip per workspace, and uses the repository's own pnpm scripts for the build and type checks, so dependencies are judged in the workspace that declares them.