What the agent does
The agent clones the repository into its own cloud sandbox and reads how the app is wired before it trusts any tool. In an Angular app like realworld-apps/angular-realworld-example-app, package.json pins @angular/core 21.2.12, @rx-angular/cdk and @rx-angular/template 21.0.0, marked ^17.0.1 and rxjs ^7.8.2, and the scripts are build (ng build), test (vitest), test:coverage, test:e2e (Playwright, with @security tests split out) and format:check (Prettier). The lockfile is bun.lock, the only workflow that checks code is .github/workflows/lint.yml, which runs bun run format:check, and README.md says to run bun run setup, which initialises the realworld git submodule that angular.json reads realworld/assets/theme/styles.css from. So the agent runs git submodule update --init and bun install first, or ng build fails on a missing stylesheet before any analysis starts.
Then it maps the entry points, because that is where dead-code tools go wrong on Angular. angular.json has one project whose build target starts at src/main.ts with tsconfig.app.json, and src/app/app.routes.ts loads every page lazily with loadComponent and loadChildren: home, tag/:tag, login, register, settings, profile (through features/profile/profile.routes.ts), editor, editor/:slug and article/:slug. Those dynamic imports are reachable code even though nothing imports them statically. vitest.config.ts includes src/**/*.spec.ts, sets src/test-setup.ts as its setup file and aliases @ to src, and the six spec files all test services: articles, comments, tags, profile, user and jwt. The agent configures knip with its Angular plugin so angular.json, the route files, the Vitest setup and the specs count as entries, and excludes the realworld submodule, which is the shared spec and theme, not app code.
The candidates come from three places and each one is checked, not reported on faith. For files and exports, the agent walks src/app: core/models/loading-state.model.ts and core/models/errors.model.ts, the three interceptors in core/interceptors, the if-authenticated.directive.ts, the default-image.pipe.ts and markdown.pipe.ts under shared/pipes, the article-list-config.model.ts and the favorite-button and follow-button components. An Angular template can reference a standalone component or pipe only through the imports array of the component that uses it, so a pipe listed in no imports array is dead even if a file imports its class. For dependencies, it checks whether @angular/animations is injected anywhere, whether @angular/platform-browser-dynamic is used outside src/test-setup.ts and the specs (which would make it a dev dependency), and whether @types/marked still adds anything now that marked ships its own types; tslib stays because tsconfig.json sets importHelpers.
Each suspect is removed on a scratch branch and the agent runs bun run build and bun run test. The build matters more than usual here: tsconfig.json sets strict and angularCompilerOptions.strictTemplates, so a template that still uses a removed component or pipe fails ahead-of-time compilation with the template file and line. A candidate goes into the report only when both commands pass without it; a candidate that fails goes into a separate list with the error, which is usually a template reference knip could not see. The agent pushes nothing and opens no pull request; it writes the report as dead-code.md.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "Find dead code in this Angular app and write dead-code.md. Run git submodule update --init and bun install first, since angular.json reads styles from the realworld submodule. Read angular.json, tsconfig.app.json, vitest.config.ts and src/app/app.routes.ts, then configure knip with its Angular plugin so src/main.ts, every loadComponent and loadChildren route, src/test-setup.ts and the spec files are entry points, and exclude the realworld directory. List candidate files, exported symbols, standalone components and pipes that appear in no imports array, and package dependencies that nothing imports. Check every candidate by removing it on a scratch branch and running bun run build and bun run test; the build uses strictTemplates, so treat an AOT template error as proof the candidate is used. Write dead-code.md with sections for files, exports, components and pipes, and dependencies, each entry with its path and the command output that passed without it, and a final section listing every candidate that failed the check with the error. Do not commit, push or open a pull request.",
"resources": [
{
"url": "https://github.com/your-org/your-app"
}
]
}- 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 one file, dead-code.md, as an artifact on the session. Reading the session returns a download link for it that works for an hour, and reading again returns a fresh link. The report has four sections of things that are safe to delete: source files nothing reaches from src/main.ts or a lazy route, exported symbols nothing imports, standalone components and pipes that appear in no imports array, and entries in package.json that nothing imports or that only the test setup imports. Every line names the path, the ng build and vitest runs that passed with it gone, and for a dependency, whether it should move to devDependencies rather than be removed.
A fifth section lists the suspects that failed the check with the error each one produced. On an Angular app the usual entry there is a component or pipe that no TypeScript file imports but a template uses through an imports array, which the strict template compiler reports with the template path. That section is where the knip configuration needs another entry point or an ignore, and the report says which.
Before acting on it, a reviewer checks that the entry points match how the app is actually served, since a route added only in a feature branch would make its component look dead on the default branch, and that the Playwright specs under the submodule did not reference a removed debug hook such as window.__conduit_debug__, which src/app/app.config.ts sets for the end-to-end tests and which no unit test touches.
Run it on every first Monday of the month
Dead code builds up a route at a time, so one report a month is enough. A cron job on your server sends this request on the first Monday of each month with a request_id made from the date, so a retried request returns the run it already started instead of starting another. Each run begins from a fresh clone, so the report describes the default branch as it is that morning, submodule included.
Give the key a webhook URL and Rush POSTs run.completed when the turn finishes, with data.session_url. Your handler reads the session from that URL, takes the dead-code.md link from its artifacts and files it as an issue or posts it to the channel where the team plans cleanup.
The use case this job follows, with its automation and trigger: Produce a report or file, no repo.
Why run it on Rush?
Proving an Angular file is dead means deleting it, rebuilding with the strict template compiler and running the suite, once per suspect. On a laptop that is an afternoon of stashing and waiting on ng build, with the risk of leaving a half-deleted working tree behind. In a Rush sandbox the agent does every delete-and-rebuild cycle on its own clone with its own node_modules and submodule checkout, and nothing it removes touches your branches.
The report is only as good as its evidence, and the evidence is kept. The transcript at GET /o/{org}/sessions/{id}/messages holds every ng build and vitest invocation with its output, including the template errors that saved a false positive, so a reviewer can see why each line is in the report without re-running anything. Several apps, or several branches of one app, run at once in their own sandboxes.
Frequently asked questions
Will it count lazily loaded routes as dead?
No. The agent reads src/app/app.routes.ts and features/profile/profile.routes.ts and registers every loadComponent and loadChildren target as an entry point before knip runs. A page that only a route reaches is live, and the build check would catch a mistake anyway, because ng build compiles every route target.
What about components and pipes that only templates use?
A standalone component or pipe reaches a template only through the imports array of the component that uses it, so the agent checks those arrays, not only TypeScript imports. If a candidate is used in a template the agent missed, strictTemplates fails the build with the template file and line, and the candidate lands in the failed section instead of the safe list.
Does it need the realworld submodule?
Yes, to build. angular.json lists realworld/assets/theme/styles.css under styles and playwright.config.ts points testDir at realworld/specs/e2e, so the agent initialises the submodule before the first build and excludes it from the analysis, since it is shared RealWorld material rather than app code.
Will it remove anything or open a pull request?
Not with this prompt. The deletions happen on a scratch branch inside the sandbox only to run the checks, and the report is the result. Send a follow-up to the same session asking it to delete the items in the safe list and open a pull request, and it picks up the same clone with the report already in hand.
Can it also report dependencies that belong in devDependencies?
It does. A package that only src/test-setup.ts or the spec files import, which is what @angular/platform-browser-dynamic looks like at first glance, is listed as a move rather than a removal, with the files that import it, and the same check is run after the move.