transfer is the single-command, project-scoped path. It chains the four phases of a migration — extract → structure → mappings → migrate — into one call, then writes a PDF summary on completion. Use it when you have one project (or a small, well-known set of projects) to move across.
Unlike a full migrate, transfer only touches the specified project and the entities it actually uses — its quality gate, its quality profiles, its permissions and project settings, and its complete issue and Security Hotspot history (including externally imported issues). Instance-wide entities such as portfolios, global settings, permission templates, and default gate/profile selection are not modified. See What gets migrated below.
If you need fine-grained control, want to review the intermediate files between phases, or are migrating many projects across multiple SonarQube Server instances, see Using migrate instead.
- Migrating a single project from SonarQube Server to SonarQube Cloud.
- Quick one-off moves where you don't need to review intermediate files.
- Smoke-testing the tool against a known project before a larger migration.
If any of these sound like you, jump to MIGRATE.md instead:
- Multiple SonarQube Server instances.
- You want to inspect or edit the mapping CSVs before pushing.
- You want to run the phases at different times (e.g., extract on a Friday, migrate on a Monday).
- You want to resume a partial migration after a failure.
Behind the scenes, transfer runs the same four phases as the manual workflow, in order:
- Extract — connects to SonarQube Server and pulls the project's configuration and its full issue/hotspot project data (project data is always included for
transfer). - Structure — assembles the extracted data into the project + org structure.
- Mappings — generates the per-entity mapping CSVs (gates, profiles, groups, templates, portfolios).
- Migrate — applies the project-scoped subset to SonarQube Cloud: it runs only the tasks needed for the project, its quality gate and profiles, its permissions, and its issue/hotspot history. Their dependencies are resolved automatically; global/instance-wide tasks are skipped.
On completion, a migration summary is written into the export directory as both migration_summary.pdf and migration_summary.md, alongside the run instrumentation files (run_meta.json, run_events.jsonl). These are written even when the run fails, so the summary can explain the failure.
transfer migrates a project-scoped slice, not the whole instance.
Included:
- The specified project (created in the target organization).
- The quality gate the project uses, with its conditions.
- The quality profiles the project uses, with their rules restored (and any parent relationships).
- The project's permissions (group permissions), settings, tags, links, webhooks, and new code period.
- The project's complete issue history — both native SonarQube issues and externally imported issues (from third-party analyzers) — replayed via project-data import, with triage state (status, resolution, assignee, comments, tags) synced afterward.
- The project's Security Hotspots, with their review status and comments synced.
- The project's DevOps platform (ALM) binding — the project is bound on SonarQube Cloud to the repository it was bound to on SonarQube Server (issue #122). Only the project-level binding is replicated: the organization's own DevOps platform binding needs secrets that cannot be migrated and is read-only input here. The binding is attempted only when the source project is bound and the target organization is itself bound to the same platform; otherwise the project is reported as a partial migration explaining why. See What gets migrated → DevOps platform bindings.
Not modified (use the full migrate command for these):
- Portfolios.
- Global settings, global webhooks, and the global new code period.
- Permission templates and default-template assignment.
- Organization-level and profile-level group permissions.
- Default quality gate / default quality profile selection.
- Rule tag and rule description updates.
Note on prerequisites. A few global entities are created on the target only because the project depends on them — for example, the groups referenced by the project's group permissions, and the migration user/permissions used to perform the migration. These are created as needed so the project's own configuration resolves correctly.
Note on issue counts. The target issue count is normally lower than the SonarQube Server total because issues that are CLOSED or resolved as FIXED have no SonarQube Cloud counterpart and are intentionally skipped (the scanner report only recreates active findings). Open issues plus triaged ones (won't-fix / false-positive / accepted) and all externally-imported issues are migrated. Security Hotspots transfer in full, but they arrive as issues (see above) — so they are counted inside the target issue total, and SonarQube Cloud's Security Hotspots view will be empty by design. Comparing a source hotspot count against a target hotspot count therefore always reads as total loss even when every hotspot migrated correctly; filter the target project by the
sqs-hotspottag instead.
Non-main branches. Project-data import now migrates the project's non-main branches too — each is created on SonarQube Cloud as a long-lived branch with its full issue history. Before submitting a non-main branch's report, the tool performs SonarQube Cloud's "Create analysis" handshake (
POST {api-host}/analysis/analyses) to register the branch and obtain an analysis id, which it embeds in the report so the Compute Engine binds the issues to the branch. All migrated branches are registered as long-lived so SonarQube Cloud's automatic pruning of short-lived branches (after ~30 days) never discards migrated history. A non-main branch is skipped only when the source server no longer has its source code (e.g. purged by housekeeping for an inactive branch) — re-analyze that branch on the source first to restore it.Branch cap. At most 10 long-lived branches per project are migrated — a safeguard against projects with heavy branch sprawl (little branch housekeeping, or a branch selection that casts too wide a net) taking too long to migrate or putting too much pressure on SonarQube Cloud's API. When a project has more than 10 eligible branches, the main branch and any branch literally named
masterordevelopare always kept, then branches matching[Rr]elease.*(most recently analyzed first), then the remaining branches (also most recently analyzed first) fill any leftover slots. Branches dropped by the cap are named in the migration report's Details column for that project. This limit does not apply toextract, and is not configurable — it is a fixed internal safeguard.
transfer replicates the project's DevOps platform binding so the migrated project is linked to the
same repository on SonarQube Cloud. The identifier carried over per platform is:
| Platform | Identifier migrated |
|---|---|
| GitHub | Repository name (owner/repo) |
| GitLab | Project id |
| Azure DevOps | Project name + repository name |
| Bitbucket Cloud | Repository slug |
Preconditions. Two must both hold, and both are checked before any write:
- The source project is bound on SonarQube Server (
GET /api/alm_settings/get_binding). An unbound project is simply left unbound on the target — nothing is reported. - The target organization is bound to the same DevOps platform
(
GET /api/alm_integration/show_bound_organization). SonarQube Cloud can only bind a project to a repository of the DevOps organization its own organization is bound to.
When the source project was bound but the target organization is not bound to that platform, the project's migration outcome becomes Partial Migration and the report's Details column reads "project binding was not possible because the org itself is not bound". The same happens, with a different sentence, when the organization is bound but the repository does not exist in the bound DevOps organization.
Both preconditions are best-effort and never fail the migration (issue #505). Reading them only enables this optional extra, so any failure degrades to "no binding" and the run continues:
| What the target answers | Recorded as | Report Details |
|---|---|---|
show_bound_organization → HTTP 500 (SonarQube Cloud's normal answer for an org with no DevOps binding), 404 (no such org), 400/403 (token cannot administer it) |
unbound | "...because the org itself is not bound" |
show_bound_organization → any other failure (transport error, 502/503, ...) |
binding unknown | "...because the target organization's DevOps platform binding could not be read" + the API error |
list_repositories → HTTP 400 "This organization is not bound to an ALM application" / 403 / 404 |
no repositories | the unbound-org sentence above (reported from the org binding) |
list_repositories → any other failure |
repositories unknown | "...because the repositories of the bound DevOps organization could not be listed" + the API error |
Only a cancelled or timed-out run still aborts these tasks. The distinction between "unbound" and
"unknown" is deliberate: before #505 an unbound org's HTTP 500 aborted the entire migrate run with
phase 2: task getOrgBinding: ..., and reporting an unread binding as "not bound" would state
something the tool never observed.
On-premise DevOps platforms are never migrated. SonarQube Cloud integrates only with the
cloud platforms — GitHub.com, GitLab.com, Azure DevOps Services and Bitbucket Cloud — so a
source project bound to GitHub Enterprise Server, self-managed GitLab or Bitbucket Server/Data
Center has no target equivalent. Cloud vs on-premise is decided from the source ALM setting's url
(its API endpoint: api.github.com, gitlab.com, dev.azure.com, visualstudio.com for Azure
DevOps Services accounts predating the rename, bitbucket.org). Such a project is reported as
Partial Migration with "project binding was not possible because the source project is bound to
an on-premise DevOps platform, which SonarQube Cloud cannot integrate with" — before #505 the
binding was dropped silently and the project was reported as fully migrated.
A project that is not bound at all on the source is still left unbound on the target with nothing reported, which is the #122 behaviour.
sonar-migration-tool transfer -c config.jsonconfig.json uses the same unified shape as extract and migrate — one top-level block of shared defaults plus source and target sub-objects. See ADVANCED-CONFIG.md for the full reference.
Minimal form:
{
"source": {
"url": "https://sonarqube.example.com",
"token": "sqp_xxx"
},
"target": {
"token": "squ_xxx",
"default_organization": "my-org"
}
}Full form:
{
"concurrency": 25,
"timeout": 60,
"export_directory": "./migration-files",
"project_key_regexp": "my-project",
"source": {
"url": "https://sonarqube.example.com",
"token": "sqp_xxx",
"pem_file_path": "/path/to/cert.pem",
"key_file_path": "/path/to/cert.key",
"cert_password": "optional"
},
"target": {
"url": "https://sonarcloud.io/",
"token": "squ_xxx",
"default_organization": "my-org",
"enterprise_key": "my-enterprise"
}
}sonar-migration-tool transfer \
--source_url https://sonarqube.example.com \
--source_token sqp_xxx \
--project_key_regexp my-project \
--target_token squ_xxx \
--default_organization my-org--project_key_regexp is always compiled as a full-match regex, implicitly
anchored with ^ and $ (issue #529). A plain key like my-project matches
only itself, so single-project usage is unaffected. A pattern transfers every
source project whose key fully matches it in one run:
# Transfers every project whose key starts with "BANKING_" — not a key
# that merely contains "BANKING_" somewhere in the middle.
sonar-migration-tool transfer \
--source_url https://sonarqube.example.com \
--source_token sqp_xxx \
--project_key_regexp "BANKING_.+" \
--target_token squ_xxx \
--default_organization my-org--project_key_regexp (or its deprecated alias --project_key, or
project_key_regexp/project_key in the config file) is required —
transfer is project-scoped by design. Use the step-by-step migrate workflow
(see MIGRATE.md) to transfer every project in one run.
Note:
transferrunsstructureitself, which rewritesprojects.csv, so the per-project organization override described in Mapping unbound SonarQube Server projects cannot be used withtransfer. Use the step-by-stepmigrateworkflow when you need it. (Issue #612.)
| Flag | Config key | Description |
|---|---|---|
-c, --config |
— | Path to a JSON configuration file (see ADVANCED-CONFIG.md) |
--source_url |
source.url |
SonarQube Server URL |
--source_token |
source.token |
SonarQube Server token |
--project_key_regexp |
project_key_regexp |
Project key (or regexp) to transfer (required; transfer is project-scoped by design). Always compiled as a full-match regex, implicitly anchored with ^ and $ — a plain key matches only itself; a pattern like BANKING_.+ transfers every project whose key starts with BANKING_. The deprecated --project_key / project_key still work but log a warning; --project_key_regexp takes precedence if both are set. |
--target_url |
target.url |
SonarQube Cloud URL (default: https://sonarcloud.io/) |
--target_token |
target.token |
SonarQube Cloud token |
--default_organization |
target.default_organization |
SonarQube Cloud organization key |
--enterprise_key |
target.enterprise_key |
SonarQube Cloud enterprise key (defaults to --default_organization) |
--export_dir |
export_directory |
Working directory for intermediate files (default: ./migration-files/) |
--concurrency |
concurrency |
Max concurrent HTTP requests (default: 25) |
--timeout |
timeout |
HTTP request timeout in seconds |
--pem_file_path |
source.pem_file_path |
Client mTLS PEM file for the source server |
--key_file_path |
source.key_file_path |
Client mTLS key file for the source server |
--cert_password |
source.cert_password |
Password for the source server mTLS client certificate |
--insecure |
source.insecure |
Skip TLS certificate verification for the source SonarQube Server connection. For a trusted internal server whose certificate is self-signed or not signed by a trusted CA; leaves the connection open to man-in-the-middle interception. Defaults to off. Issue #586. |
--skip_project_data_migration |
top-level skip_project_data_migration |
Skip the project-data migration (importProjectData + per-issue / per-hotspot sync). Defaults to off — project data is migrated by default. Issue #303. |
--exclude_branches |
target.exclude_branches |
Glob patterns for non-main branches to skip during project data import. Repeatable. Main branch is never excluded. |
--branch_regexp |
branch_regexp |
Regexp pattern of branch names to extract/migrate, always compiled as a full-match regex, implicitly anchored with ^ and $ — a plain name matches only itself. Applies to both phases of the transfer. Omit to process every branch. The main branch is always included regardless of match. Issue #582. |
--unsupported_languages |
top-level or target.unsupported_languages |
How to handle files whose language has no quality profile on the target — typically a language from a 3rd-party SonarQube Server plugin. exclude (default) drops those files from the analysis report so the rest of the project still migrates; skip does not migrate the project's issues/branches at all; warn submits the report unchanged. Issue #474. |
--migrate_history |
top-level migrate_history |
PoC. Also migrate a bounded set of historical analysis snapshots (date + project-level measures only) per migrated branch (every branch, not just main — #625), backdated on SonarQube Cloud. Defaults to off — no change to existing behavior unless set. Issue #554. |
--history_max_points |
top-level history_max_points |
Max historical snapshots migrated per branch (not per project — each migrated branch gets its own cap) when --migrate_history is set (default: 0, no cap — every analysis is a candidate). |
--history_min_interval_days |
top-level history_min_interval_days |
Minimum spacing, in days, enforced between two migrated historical snapshots of the same branch when --migrate_history is set (default: 0, no spacing rule). |
--branch_analyzed_after |
source.branch_analyzed_after + target.branch_analyzed_after |
Only select branches analyzed on or after this YYYY-MM-DD date. The project's main branch is always selected regardless. Omit to select all branches (default). Issue #583. |
CLI flags override values from the config file when both are provided.
--branch_analyzed_after is applied to both the extract and migrate phases at once when passed on the CLI — unlike --exclude_branches (migrate-only), a transfer talks to both sides in one invocation. Giving the two phases genuinely different cutoffs (per one of the worked examples in ADVANCED-CONFIG.md) requires the config file's source.branch_analyzed_after / target.branch_analyzed_after; there is deliberately no fallback from one side to the other, so setting only one leaves the other phase unfiltered.
SonarQube Server can analyze languages SonarQube Cloud cannot. A language contributed by a 3rd-party (non-SonarSource) plugin has no analyzer on the Cloud side, and therefore no quality profile.
The analysis report transfer fabricates stamps every file with the language
the source server reported for it, while the report's metadata can only name
quality profiles that exist on the target. When a file's language has no target
profile, the SonarQube Cloud Compute Engine rejects the entire report:
Report contains a file with language 'lua' but no matching quality profile
The project, its permissions and its quality gate are created before the report is submitted, so the result is a project that looks migrated but has no issues and no branches.
transfer detects this before submitting and prints the affected languages, the
file count and an example path. Choose the handling with
--unsupported_languages:
| Mode | Behaviour |
|---|---|
exclude (default) |
Drops the unsupported-language files from the report. Everything else — the other files, their issues, measures and all branches — migrates. The project is reported as a Partial Migration with the languages and file count listed. |
skip |
Submits no report for the project. Its settings, permissions and quality gate still migrate; its issues and branches do not. Reported as skipped, with the reason. |
warn |
Submits the report unchanged (pre-#474 behaviour). The Compute Engine is expected to reject it; the rejection is reported as such rather than as a generic API error. |
# Migrate everything except the unsupported-language files (default)
sonar-migration-tool transfer -c config.json --project_key_regexp my-project
# Do not transfer this project's issues/branches at all
sonar-migration-tool transfer -c config.json --project_key_regexp my-project \
--unsupported_languages skipA failure to read the target organization's quality profiles disables the detection entirely rather than treating every language as unsupported, so a transient API error can never drop a project's files.
Since #557, every branch's regular current-snapshot import stamps its
analysis with the source branch's real last-analysis date — read from
api/project_branches/list's analysisDate field — instead of the migration
run's own wall-clock time. This is unconditional: it applies to every
branch whether or not --migrate_history is set, unlike the historical
points described below. Only the analysis's own stamped date changes; other
fallback dates used elsewhere in the import are untouched.
This is a proof-of-concept. By default, transfer (and migrate) submit a
single scanner report per branch. Since #557, that report is backdated to the
source branch's real last-analysis date instead of "now" (see
Current-snapshot backdating above) — but
without --migrate_history, it is still only one point: the target's analysis
history starts there, even if the source project has years of prior analyses.
Issue #554 asks for a way to carry some of that history over.
--migrate_history opts into replaying a bounded set of a project's
historical analyses — on every migrated branch, not just main (#625) —
as separate, backdated entries on the target, submitted before each branch's
regular current-snapshot import so each lands as its own point in SonarQube
Cloud's analysis history (/api/project_analyses/search), not just a
re-dated copy of the latest one. A non-main branch's historical points
perform the same "Create analysis" handshake (see
TRANSFER-INTERNALS.md) the regular current-snapshot
import already performs once per report — once per historical point here,
not once per branch, mirroring what a real scanner does before every
analysis upload.
Each historical entry carries the project's own measures as recorded by the
source server at that analysis: lines of code, complexity, comment density,
duplication, and — since #557 — coverage itself, not just its raw inputs.
The Compute Engine only ever computes coverage from a component's real
per-line coverage data, never from a pushed aggregate measure (confirmed
live: pushing lines_to_cover etc. as plain measures, #557's first attempt,
was silently ignored), so each historical point's placeholder file carries
synthetic per-line coverage records — arbitrary which lines are marked
covered, since the file is never viewed, but built so the totals match the
source's real figures exactly.
Duplication turned out to hide the identical bug. duplicated_lines,
duplicated_blocks and duplicated_files were also silently ignored when
pushed as plain measures, so duplicated_lines_density (itself a formula
over duplicated_lines/lines) never actually computed — invisible until
now because the reference project used to validate this PoC has had 0%
duplication for its entire history, so "shows 0" looked correct without
being computed at all. Fixed the same way as coverage: each historical
point's placeholder file also carries a synthetic same-file duplication
block — one origin line range and one duplicate line range within that same
placeholder file, since there is no second real file to duplicate against —
sized so the reconstructed duplicated_lines/duplicated_blocks/
duplicated_files match the source's real figures exactly.
Historical points now also carry real bugs, vulnerabilities and
code_smells counts, reliability_rating/security_rating, and technical
debt (sqale_index) — reconstructed the same way coverage and duplication
are: the Compute Engine derives all of these exclusively by counting real
Issue-shaped entries in the submitted report, never from a pushed aggregate
measure. So for each historical point the tool fabricates that many
placeholder issues — one per bug/vulnerability/code smell the source
recorded at that analysis — submitted through SonarQube Cloud's
external-issue mechanism (for third-party/ad-hoc findings, distinct
from native rule-based issues) under fixed ad-hoc rules (smt-history:bug,
smt-history:vulnerability, smt-history:code_smell). These are exactly
as synthetic as the placeholder file itself: no real code location or
message, just whatever severity reproduces the source's real historical
rating (ratings threshold on the single worst severity present, not an
average, so one issue at the right severity is enough to make this exact
rather than approximate) and, for code smells, however much effort
reproduces the real sqale_index (confirmed live that only code-smell
effort drives sqale_index, not bug/vulnerability effort). External issues
were chosen specifically because, unlike native issues, they need no active
rule in the target's resolved quality profile.
What still can't come across is the original issues themselves — their file, line, message and identity — and Security Hotspots. Both remain a hard SonarQube API limitation, not a scope choice: there is no API that returns "what issues existed as of a past analysis," so the real, individual findings cannot be reconstructed for a historical point — only the aggregate counts/ratings/debt above, which the source does expose historically, via the same measures-history endpoint already used for coverage/ncloc/etc. Hotspots specifically stay unreconstructed even in aggregate: unlike bugs/vulnerabilities/code smells, converting a hotspot to an issue needs a real active rule in the target's resolved quality profile — exactly the constraint the external-issue mechanism above exists to sidestep, which is why it can't be reused for hotspots. SonarQube also only keeps issues (and hotspots) attached to a branch's most recent analysis anyway, which is exactly what the existing, unchanged current-snapshot import already migrates in full.
Two flags can bound how much history is walked, applied to each migrated
branch's own full analysis list on the source (per branch, not once per
project), oldest to newest, always dropping that branch's single most recent
analysis (already covered by the current-snapshot import). Left unset, both
default to 0 — no cap, no spacing — so every analysis becomes a candidate:
| Flag | Default | Meaning |
|---|---|---|
--history_max_points |
0 |
At most this many historical snapshots per branch — each migrated branch gets its own cap, so a project with N migrated branches can replay up to N × this many; 0 (the default, whether passed explicitly or left unset) means no cap — every candidate analysis is migrated. When set above 0 and a branch has more candidates than that after interval bounding, they are evenly resampled across that branch's whole history span, not just the oldest end. |
--history_min_interval_days |
0 |
Two selected snapshots of the same branch are never closer together than this; 0 (the default, whether passed explicitly or left unset) means no spacing rule at all — every analysis in a branch's source history becomes a candidate, including several on the same day at different times. |
By default, both flags are left unset — and an unset flag now behaves exactly like explicitly passing
0: every analysis on each migrated branch of the source becomes a history candidate, with no cap and no minimum spacing. These flags exist for callers who want to dial history down from that exhaustive default to something bounded or spaced out, not the other way around — there is nothing "denser" than the default to opt into. Here's what dialing the spacing up costs you, on a source branch with 134 analyses spanning 2021→2026:
--history_min_interval_daysPoints selected 0133 (every analysis but the newest) 1105 771 3031 Bear in mind each point is a separate report submission plus a Compute Engine poll — roughly 11.5s (the CE poll interval is 10s, #571) — so the exhaustive default is a long migration on a project with a lot of history; use
--history_min_interval_daysand/or--history_max_pointsto bound it deliberately.
# Migrate the current snapshot as usual, plus every historical analysis on
# every migrated branch — unbounded, unspaced (the default when the flags are left unset)
sonar-migration-tool transfer -c config.json --project_key_regexp <projectKeyRegexp> \
--migrate_history
# Bounded history instead: at most 10 points per branch, at least 30 days apart
sonar-migration-tool transfer -c config.json --project_key_regexp <projectKeyRegexp> \
--migrate_history --history_max_points 10 --history_min_interval_days 30Known limitations (PoC):
-
Best-effort, not transactional. If a historical submission is rejected by the Compute Engine, history migration for that branch stops and logs a warning — it never fails or blocks the regular current-snapshot import that follows it.
-
Resume-safe. A point dated at or before the branch's newest analysis on the target, or at or after the date the regular import is stamped with, is dropped before submission and logged at Info: the Compute Engine refuses both, and the second would make the regular import itself fail. A re-run therefore only sends what the target is missing, and a branch that is already up to date is skipped entirely.
-
Not every migrated point stays on the target. The Compute Engine accepts and writes every point the migration submits, but SonarQube Cloud then removes some of them. Expect the Activity page to hold fewer analyses than were migrated — on a 134-analysis source project, 133 points were submitted, all 133 were accepted, and 61 remain.
This is target-side behaviour, not a migration failure, and it was confirmed by direct observation rather than inferred. A throwaway project was seeded with exactly one point dated 2021-06-13 and polled sub-second:
15:24:14Z CE task → SUCCESS 15:24:14.437Z ┐ 12 consecutive HTTP-200 polls of │ /api/project_analyses/search list the analysis, and 15:24:29.659Z ┘ /api/qualitygates/project_status?analysisId=… returns 200 15:24:31Z the next CE task (the regular current-snapshot import) runs 15:24:31.203Z same endpoint, same query → the analysis is gone, and its analysisId returns 404 permanentlyA control run, killed before any subsequent analysis could succeed, still holds its 2021-06-13 analysis. So the row is created and readable, then deleted — it is not rejected at ingestion, and it is not merely hidden by the API.
What decides which points are removed is not established. The oldest points go first, which is consistent with the documented housekeeping retention window, but the source used for testing has an 87-day gap around the apparent boundary, so the data cannot distinguish 260 weeks from 5 calendar years — or from any other value in between. Removals were also observed well inside any retention window. Treat the surviving count as something to measure on your own target, not to predict.
Practically: migrating more points always costs proportional wall clock, and past some point the target discards the extra — so if you don't need exhaustive history, dial down with
--history_min_interval_daysand/or--history_max_pointsrather than paying for points that won't survive. -
Each historical entry carries one placeholder file. A report holding a lone project component with a raw measure is rejected by the Compute Engine, so every historical analysis includes a single empty
__history_snapshot__.<ext>component for the measures to attach to. Its language is chosen from the ones the target organization actually has a quality profile for. The file is never meant to be read, but it is part of the analysis. -
Requires both
extractandmigrateto have--migrate_history(or themigrate_historyconfig key) set —transfersets both automatically; running the two commands separately needs the flag on each.
- Intermediate files — written to
--export_dir(default./migration-files/). Same files as the manual workflow:organizations.csv,gates.csv,profiles.csv,groups.csv,templates.csv,portfolios.csv. migration_summary.pdf— PDF migration summary, written to the export directory on completion.migration_summary.md— Markdown rendering of the same summary, written alongside the PDF on completion.run_meta.json— per-phase / per-task timing andoverall_status(success|partial|failed) for the run, written to the run directory on completion (including failed runs, so the summary can explain the failure).run_events.jsonl— JSON Lines stream of run events (one record per line) mirrored from the logger by the tee slog handler; the summary collector parses these to build the report.- Stdout — every command prints
See sonar-migration-tool output results in <directory>when it finishes so you always know where to look.
For a full description of every output file, see the Output Files Reference in MIGRATE.md.
- Log in to SonarQube Cloud and confirm the project appears under the target organization.
- Spot-check that the quality gate and quality profile are present.
- Spot-check that issues came across (compare counts against the source). Former Security Hotspots are part of that issue count — filter the target project by the
sqs-hotspottag to see them. Do not compare against SonarQube Cloud's Security Hotspots view: it is empty by design, because Cloud no longer has hotspots. Project data is always imported, so a fresh re-scan is not required to seed historical data — though you should still run a normal analysis once your pipeline is repointed. - Update your CI/CD pipeline to point at SonarQube Cloud (
SONAR_TOKEN,SONAR_HOST_URL).
For more on post-migration steps, see the After you migrate section in MIGRATE.md.
- Token errors — see the Token permissions section in MIGRATE.md.
- Org not found — confirm
--default_organizationmatches an existing organization in your SonarQube Cloud enterprise. - Anything else — TROUBLESHOOTING.md has the full list of common errors.