Short answer
A long-lived, per-project Qodana Cloud token stored as a masked CI secret is a defensible least-privilege posture for most teams, because the token's blast radius is limited to uploading results for one project. Scheduled rotation is worth adding only if you can automate it; manual rotation across many pipelines tends to fail silently and causes exactly the outage it was meant to prevent. Whichever you choose, the real gap is failure visibility: you should fail the job on upload errors and alert on missing reports, because the linter's quality gate does not depend on the upload succeeding.
Two caveats up front: Qodana's token and failure behavior has changed across releases, so verify against your pinned linter/Action version rather than assuming the details below, and Qodana does not currently provide a built-in token rotation mechanism, so "rotation" always means your own automation plus a secret update.
1. Long-lived token vs rotation
Reasoning through the trade-off rather than citing a rule:
- Scope is already narrow. A project token authorizes uploads to one project. A leak lets an attacker push forged or spam results to that dashboard — annoying and a data-integrity problem, but not a path into your source code or other projects.
- Rotation cost scales with repo count. Without an API-driven rotation flow, each rotation is a manual secret edit per pipeline. At dozens of repositories, rotations get skipped, and a half-rotated fleet means some pipelines upload with the old token and fail.
- Rotation pays off when automated. If your org already runs a secrets manager (Vault, AWS Secrets Manager, Doppler) that CI reads at job start, rotation is nearly free and strictly better. If secrets are hand-edited in the GitHub/GitLab UI, a long-lived token with a documented revocation runbook is the more honest policy.
Practical middle ground: one token per project (never shared across projects), stored as a masked/protected CI secret, plus a calendar-driven rotation (e.g., quarterly) that you can execute in minutes because you've scripted the secret update.
2. What happens when the token is expired or revoked
Your understanding is correct on the core point: analysis and the quality gate are evaluated locally by the linter, so a bad token does not stop the scan or change the gate result. Only the upload to Qodana Cloud fails.
Whether that failure fails the job depends on the integration and its version, and this is the part to verify rather than assume:
- In the GitHub Action, upload errors have historically surfaced as warnings in some versions and hard failures in others; check the action's inputs for anything controlling upload failure behavior and test it.
- In the Docker-based integration, the container's exit code reflects the linter run; inspect whether an upload failure changes the exit code in your pinned image tag.
A 5-minute experiment settles it for your setup: point a test pipeline at a project with a deliberately invalid token and observe the step outcome:
# Docker example — expect analysis to succeed, upload to fail
docker run --rm \
-e QODANA_TOKEN="deliberately-invalid" \
-v "$PWD":/data/project \
jetbrains/qodana-jvm:latest
# Then: echo $? and check whether the step/job is marked failed
If the job does not fail on upload error, add an explicit check — for example, grep the log for the upload result line, or gate on the presence of the uploaded report — so a dead token is loud instead of silent.
3. Detecting stale uploads
There is no widely documented built-in "missing report" alert in Qodana Cloud, so treat this as your own monitoring problem. Two workable patterns:
- Fail loudly at the source (preferred): make the CI job fail when upload fails, per the test above. This converts silent staleness into a red pipeline, which your existing CI notifications already cover.
- External freshness check: a scheduled job that verifies each project received a report within the expected window (e.g., daily for nightly scans). If Qodana Cloud's API in your plan exposes report history, query it; otherwise, a lightweight proxy is "did the nightly upload job succeed in CI," which your CI system's API can answer.
Recommendation
Keep long-lived per-project tokens, store them as protected CI secrets, document a revocation runbook, and invest the effort you would have spent on manual rotation into making upload failures fail the job. Revisit automated rotation only if you adopt a secrets manager that CI reads at runtime. Verify the exact upload-failure behavior against your pinned Action/image version — that's the one detail that changes the answer, and only you can test it in your environment.