All jobs

Find Dead Code in a Flask App

Yes. An agent runs vulture and your linters on a Flask app, checks each candidate against templates, plugin hooks and CLI commands, and writes a report.

Run this on RushIn a Project that has your repository.

What the agent does

Dead-code tools lie on a Flask app unless someone checks what the framework calls by name, so the agent starts by learning how the app registers things. In a forum like flaskbb/flaskbb, flaskbb/app.py builds the application in create_app and a chain of configure_* functions: configure_blueprints, configure_extensions, configure_template_filters, which pushes filters and globals such as run_hook, get_management_navigation and is_htmx_request into app.jinja_env, configure_context_processors, configure_errorhandlers and load_plugins. A function that no Python module imports can still be live because a Jinja template under flaskbb/templates/, 68 HTML files, calls it, or because flaskbb/cli/ registers it as a Click command, or because a pluggy hook implementation in flaskbb/settings/registry.py or flaskbb/plugins/models.py is found by name.

The agent runs vulture over flaskbb/ with a whitelist it builds from those registration points: every view bound through register_view in flaskbb/utils/helpers.py, every template filter and global, every hookimpl, every Click command, and SQLAlchemy columns and relationships, which vulture otherwise reports as unused attributes. It runs ruff check too, since pyproject.toml selects F (unused imports and variables) and B, and runs mypy with the project's configuration, which sets warn_unused_ignores. What survives the whitelist is the candidate list, and each candidate gets read in context before it is reported.

Two sources of dead code are specific to this codebase and get their own sections. flaskbb/deprecation.py defines RemovedInFlaskBB4 and a deprecated decorator, and flaskbb/app.py still warns about CELERY_* settings, a deprecated CACHE_TYPE and RATELIMIT_STORAGE_URL, so the report lists every deprecated path and whether anything inside the app still calls it. The plugin system in flaskbb/plugins/spec.py declares 78 flaskbb_* hook specifications across 1,579 lines; the agent greps hook.<name> calls in Python and run_hook("<name>") calls in templates and lists the specifications nothing calls, which is where template hooks such as flaskbb_tpl_profile_links and flaskbb_tpl_form_registration_before land if no template invokes them. A hook that nothing calls is dead for every plugin that implements it.

For each candidate it reports as safe, the agent proves it on a scratch branch: delete the function, run uv run pytest with the -n auto distribution pyproject.toml sets, run ruff check and mypy, and keep the candidate only if all three pass; the 76 test files under tests/unit and tests/endtoend are the oracle. It writes dead-code.md, pushes nothing and opens no pull request: deleting is a decision for the maintainers, made with the file and line of each item in front of them.

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 Flask application and write dead-code.md. Read flaskbb/app.py, flaskbb/utils/helpers.py, flaskbb/plugins/spec.py, flaskbb/cli/main.py, flaskbb/deprecation.py and pyproject.toml first and list how views, template filters, Jinja globals, Click commands and pluggy hooks are registered by name. Run vulture over flaskbb/ with a whitelist built from those registration points and from SQLAlchemy columns, then ruff check and mypy with the project's configuration. Read every remaining candidate in context. For each one you call safe, delete it on a scratch branch and run uv run pytest, ruff check and mypy; keep it in the safe list only if all three pass. Add a section for deprecated code paths under flaskbb/deprecation.py and the warnings in flaskbb/app.py, and a section for hook specifications in flaskbb/plugins/spec.py that no Python code or template calls. Give file, line and signature for every item and the commands that proved it. Do not push and do not open a pull request.",
  "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 dead-code.md as an artifact on the session. It has four parts: functions, methods and classes safe to delete, grouped by package with file, line, signature and the three commands that passed without them; deprecated paths with the version they are slated for and whether the app still calls them; hook specifications nothing calls, each with the spec.py line; and the candidates vulture flagged that turned out to be live, with the template, command or hook that keeps them alive, so the next person does not re-investigate them.

The transcript at GET /o/{org}/sessions/{id}/messages keeps the vulture output, the whitelist the agent built and every scratch-branch test run, so a maintainer can see the exact pytest summary behind a "safe" verdict. The repository is not changed; the session lists no pull request.

Before deleting anything, a maintainer reads the safe list against the plugin API: a public helper unused inside FlaskBB may still be imported by flaskbb-plugin-portal or flaskbb-plugin-conversations, which pyproject.toml installs, and the report flags items that are importable from a plugin for that reason.

Run it on every first week of a release cycle

Create an automation in the Project with this prompt and a schedule trigger, with a cron such as 0 9 1 * * in your timezone, so the report is fresh at the start of each cycle. Each fire starts a run on the current default branch, and the report sits on that run's session; compare the safe lists from two runs to see what the cycle removed.

From a terminal, the same run starts with this body in a POST /o/{org}/sessions, and the result arrives on the session or through the key's run.completed webhook; the file is at GET /o/{org}/sessions/{id}/artifacts/dead-code.md.

The use case this job follows, with its automation and trigger: Hand off from your terminal.

Why run it on Rush?

Proving a function dead in FlaskBB means a uv sync with the test, typing and pre-commit groups, then a pytest run per deletion, which the -n auto setting spreads over every core. On Rush each run gets its own sandbox and its own clone, the scratch branches stay there, and the sandbox parks when the turn ends, so a follow-up asking to recheck one item against a plugin starts from the same state.

The report is only as good as its proof, and the proof is in the transcript: the vulture output, the whitelist, each deletion and each test run. A maintainer who disagrees with one verdict can see what was tried, and several repositories, or several branches of this one, can be audited at the same time.

Frequently asked questions

We already run ruff with F401 and F841. What does this add?

Ruff finds unused imports and local variables inside a file. Vulture finds functions, methods and classes that nothing in the whole package references, and the agent then separates the real ones from the ones Flask, Jinja, Click or pluggy call by name. The scratch-branch test run is the part neither tool does.

Will it delete anything from my repository?

No. Deletions happen on a scratch branch inside the sandbox to prove a verdict and are never pushed. The output is the report; a follow-up run can turn the safe list into a pull request if you ask for one.

How does it handle the plugin hooks?

It reads flaskbb/plugins/spec.py, collects every hook.<name> call in Python and every run_hook call in a template, and lists the specifications with no caller. It also marks public helpers that a plugin could import, because the plugin packages in pyproject.toml are consumers the core's own tests do not cover.

Can a function be reachable and still appear in the report?

Yes, when the only caller is a string: a template, a Click command name, a Celery task name or a setting fixture. The report puts those in the "flagged but live" section with the string that keeps each one alive, which is why every candidate is read before it is listed as safe.