SonarQube Community has no branch analysis. Every scan published under a given project key replaces that project's single analysis, whatever revision it came from. scripts/scan-local.sh knows this and defaults to a scratch key precisely to avoid it — its header says "Scanning a feature branch under the real key would replace CI's picture of main with your working tree, silently."
.gitea/workflows/sonarqube.yml does the thing the script refuses to do. It triggers on push to main, on pull_request (opened, synchronize, reopened) and on workflow_dispatch, and the scan step publishes to sonar.projectKey=redefined-designs in every one of those cases. So each pull request, and each push to a pull request's branch, overwrites the dashboard's analysis of main with the branch.
Caught in the act
While chasing the open hotspots for #180 I found the dashboard describing a revision that is not main:
Last analysis of redefined-designs: 2026-08-27T09:16 against revision 4ff713a.
4ff713a is the tip of feature/185-tag-chip-colour, a branch whose work is already in main by a different commit.
main is fdb5b7a.
backend/src/routes/admin.ts differs between the two by 67 insertions and 29 deletions.
The visible symptom was that all three hotspots reported line numbers that landed on a comment and a blank line in main. That is recoverable if you notice it — I only noticed because the lines made no sense — but the same mechanism silently misreports everything else the dashboard shows.
Why it matters beyond confusing line numbers
The new-code period, the quality gate result, the coverage percentages and the hotspot list all describe whatever was scanned last rather than main, and nothing on the dashboard says which revision that was. A gate that went green on a feature branch reads exactly like a gate that went green on main. sonar.projectVersion=1.0.0 with a PREVIOUS_VERSION new-code period makes this worse, because the baseline does not move per branch either — see the reasoning already recorded in sonar-project.properties.
It also means #180's hotspots were, for a while, being read against the wrong file. The review conclusion holds — the three call sites exist in main too, at 345, 370 and 418 — but that was luck rather than design.
Options
Restrict publishing to main. Keep running the suites on pull requests, since that is where their value is, and either skip the scan step or run it with -Dsonar.scanner.dumpToFile / no publish. Simplest, and it makes the dashboard mean one thing.
Publish pull requests under a per-branch scratch key, the way scan-local.sh already does for local runs. Keeps analysis feedback on pull requests at the cost of projects accumulating on the server.
Leave it and document it. Cheapest, and the least honest — the dashboard would still be ambiguous, just knowingly so.
Option 1 unless there is a reason pull requests need a published analysis. Whichever is chosen, scan-local.sh's warning and the workflow should end up telling the same story, since right now the script guards against a hazard that CI walks into on every pull request.
Found by
Investigating #180's open hotspots — see #180 (comment) for the evidence trail.
SonarQube Community has no branch analysis. Every scan published under a given project key replaces that project's single analysis, whatever revision it came from. `scripts/scan-local.sh` knows this and defaults to a scratch key precisely to avoid it — its header says "Scanning a feature branch under the real key would replace CI's picture of main with your working tree, silently."
`.gitea/workflows/sonarqube.yml` does the thing the script refuses to do. It triggers on `push` to `main`, on `pull_request` (opened, synchronize, reopened) and on `workflow_dispatch`, and the scan step publishes to `sonar.projectKey=redefined-designs` in every one of those cases. So each pull request, and each push to a pull request's branch, overwrites the dashboard's analysis of `main` with the branch.
## Caught in the act
While chasing the open hotspots for #180 I found the dashboard describing a revision that is not `main`:
- Last analysis of `redefined-designs`: 2026-08-27T09:16 against revision `4ff713a`.
- `4ff713a` is the tip of `feature/185-tag-chip-colour`, a branch whose work is already in `main` by a different commit.
- `main` is `fdb5b7a`.
- `backend/src/routes/admin.ts` differs between the two by 67 insertions and 29 deletions.
The visible symptom was that all three hotspots reported line numbers that landed on a comment and a blank line in `main`. That is recoverable if you notice it — I only noticed because the lines made no sense — but the same mechanism silently misreports everything else the dashboard shows.
## Why it matters beyond confusing line numbers
The new-code period, the quality gate result, the coverage percentages and the hotspot list all describe whatever was scanned last rather than `main`, and nothing on the dashboard says which revision that was. A gate that went green on a feature branch reads exactly like a gate that went green on `main`. `sonar.projectVersion=1.0.0` with a `PREVIOUS_VERSION` new-code period makes this worse, because the baseline does not move per branch either — see the reasoning already recorded in `sonar-project.properties`.
It also means #180's hotspots were, for a while, being read against the wrong file. The review conclusion holds — the three call sites exist in `main` too, at 345, 370 and 418 — but that was luck rather than design.
## Options
1. **Restrict publishing to `main`.** Keep running the suites on pull requests, since that is where their value is, and either skip the scan step or run it with `-Dsonar.scanner.dumpToFile` / no publish. Simplest, and it makes the dashboard mean one thing.
2. **Publish pull requests under a per-branch scratch key**, the way `scan-local.sh` already does for local runs. Keeps analysis feedback on pull requests at the cost of projects accumulating on the server.
3. **Leave it and document it.** Cheapest, and the least honest — the dashboard would still be ambiguous, just knowingly so.
Option 1 unless there is a reason pull requests need a published analysis. Whichever is chosen, `scan-local.sh`'s warning and the workflow should end up telling the same story, since right now the script guards against a hazard that CI walks into on every pull request.
## Found by
Investigating #180's open hotspots — see https://gitea.bermudalamb.synology.me/bermudalamb/redefined-designs/issues/180#issuecomment-1861 for the evidence trail.
bermudalamb
self-assigned this 2026-08-27 11:29:11 -05:00
bermudalamb
added this to the Code Quality and Hardening 2 project 2026-08-27 11:29:21 -05:00
Done in 0872cad and on main. Closing manually — the commit body never carried a Closes #197, which is why this stayed open.
Option 1 was taken, as recommended. .gitea/workflows/sonarqube.yml now carries if: github.event_name != 'pull_request' on the scan step, and the same guard on the measures report — with the reasoning written out at both, so the next person reading the workflow finds out why rather than deleting a condition that looks redundant.
The suites still run on pull requests, which is where their value is. Only publishing is restricted, so the dashboard now describes main and nothing else.
scripts/scan-local.sh and the workflow tell the same story now, which they did not before: the script had always defaulted to a scratch key for exactly this reason, and CI was walking into the hazard the script existed to guard against.
One consequence worth recording, since it came up while working #307: with no analysis published from a pull request, there is no way to read coverage or the gate for a branch before it merges. That is the intended trade and the right one — an ambiguous dashboard is worse than a delayed one — but it does mean coverage questions can only be answered after a merge, from the dashboard or from the workflow log's measures step.
Done in `0872cad` and on `main`. Closing manually — the commit body never carried a `Closes #197`, which is why this stayed open.
Option 1 was taken, as recommended. `.gitea/workflows/sonarqube.yml` now carries `if: github.event_name != 'pull_request'` on the scan step, and the same guard on the measures report — with the reasoning written out at both, so the next person reading the workflow finds out why rather than deleting a condition that looks redundant.
The suites still run on pull requests, which is where their value is. Only publishing is restricted, so the dashboard now describes `main` and nothing else.
`scripts/scan-local.sh` and the workflow tell the same story now, which they did not before: the script had always defaulted to a scratch key for exactly this reason, and CI was walking into the hazard the script existed to guard against.
One consequence worth recording, since it came up while working #307: with no analysis published from a pull request, there is no way to read coverage or the gate for a branch before it merges. That is the intended trade and the right one — an ambiguous dashboard is worse than a delayed one — but it does mean coverage questions can only be answered after a merge, from the dashboard or from the workflow log's measures step.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
SonarQube Community has no branch analysis. Every scan published under a given project key replaces that project's single analysis, whatever revision it came from.
scripts/scan-local.shknows this and defaults to a scratch key precisely to avoid it — its header says "Scanning a feature branch under the real key would replace CI's picture of main with your working tree, silently.".gitea/workflows/sonarqube.ymldoes the thing the script refuses to do. It triggers onpushtomain, onpull_request(opened, synchronize, reopened) and onworkflow_dispatch, and the scan step publishes tosonar.projectKey=redefined-designsin every one of those cases. So each pull request, and each push to a pull request's branch, overwrites the dashboard's analysis ofmainwith the branch.Caught in the act
While chasing the open hotspots for #180 I found the dashboard describing a revision that is not
main:redefined-designs: 2026-08-27T09:16 against revision4ff713a.4ff713ais the tip offeature/185-tag-chip-colour, a branch whose work is already inmainby a different commit.mainisfdb5b7a.backend/src/routes/admin.tsdiffers between the two by 67 insertions and 29 deletions.The visible symptom was that all three hotspots reported line numbers that landed on a comment and a blank line in
main. That is recoverable if you notice it — I only noticed because the lines made no sense — but the same mechanism silently misreports everything else the dashboard shows.Why it matters beyond confusing line numbers
The new-code period, the quality gate result, the coverage percentages and the hotspot list all describe whatever was scanned last rather than
main, and nothing on the dashboard says which revision that was. A gate that went green on a feature branch reads exactly like a gate that went green onmain.sonar.projectVersion=1.0.0with aPREVIOUS_VERSIONnew-code period makes this worse, because the baseline does not move per branch either — see the reasoning already recorded insonar-project.properties.It also means #180's hotspots were, for a while, being read against the wrong file. The review conclusion holds — the three call sites exist in
maintoo, at 345, 370 and 418 — but that was luck rather than design.Options
main. Keep running the suites on pull requests, since that is where their value is, and either skip the scan step or run it with-Dsonar.scanner.dumpToFile/ no publish. Simplest, and it makes the dashboard mean one thing.scan-local.shalready does for local runs. Keeps analysis feedback on pull requests at the cost of projects accumulating on the server.Option 1 unless there is a reason pull requests need a published analysis. Whichever is chosen,
scan-local.sh's warning and the workflow should end up telling the same story, since right now the script guards against a hazard that CI walks into on every pull request.Found by
Investigating #180's open hotspots — see #180 (comment) for the evidence trail.
Done in
0872cadand onmain. Closing manually — the commit body never carried aCloses #197, which is why this stayed open.Option 1 was taken, as recommended.
.gitea/workflows/sonarqube.ymlnow carriesif: github.event_name != 'pull_request'on the scan step, and the same guard on the measures report — with the reasoning written out at both, so the next person reading the workflow finds out why rather than deleting a condition that looks redundant.The suites still run on pull requests, which is where their value is. Only publishing is restricted, so the dashboard now describes
mainand nothing else.scripts/scan-local.shand the workflow tell the same story now, which they did not before: the script had always defaulted to a scratch key for exactly this reason, and CI was walking into the hazard the script existed to guard against.One consequence worth recording, since it came up while working #307: with no analysis published from a pull request, there is no way to read coverage or the gate for a branch before it merges. That is the intended trade and the right one — an ambiguous dashboard is worse than a delayed one — but it does mean coverage questions can only be answered after a merge, from the dashboard or from the workflow log's measures step.