Sonar-iac fails to parse valid Azure Pipelines YAML — CommentEvent cast to NodeEvent, an off-by-one

**
This is a bug report**, with a reproducer attached. Three distinct parse failures in sonar-iac
against valid Azure Pipelines YAML, stable across two scanner majors. Five files in our
repository are silently dropped from analysis on every run.

ALM used

Azure DevOps (Azure Repos, dev.azure.com).

CI system used

Azure DevOps Pipelines, Microsoft-hosted Linux agents (ubuntu), via the SonarCloud extension tasks
SonarCloudPrepare / SonarCloudAnalyze / SonarCloudPublish 4.2.5.

Scanner command used

Invoked by the extension task (org/project keys masked). dotnet (“MSBuild”) mode:

/usr/local/bin/dotnet \
  /mnt/vss/_work/_tasks/SonarCloudPrepare_<guid>/4.2.5/dotnet-sonar-scanner-msbuild/SonarScanner.MSBuild.dll \
  begin /k:<project-key> /o:<org-key>
…
/usr/local/bin/dotnet \
  /mnt/vss/_work/_tasks/SonarCloudPrepare_<guid>/4.2.5/dotnet-sonar-scanner-msbuild/SonarScanner.MSBuild.dll \
  end

Reported by the scanner as:

SonarScanner for .NET 11.2.1
Using the .NET Core version of the Scanner for .NET

Analysis properties come from a checked-in sonar-project.properties plus a small number of computed
values passed through the task’s extraProperties. Nothing sets any sonar-iac option.

Versions

Product SonarQube Cloud (sonarcloud.io), server 8.0.0.101590
Analyzer iacIaC Code Quality and Security 2.14.0.22356 (from /api/plugins/installed)
Scanner SonarScanner for .NET 11.2.1
ADO extension tasks 4.2.5
Java 21.0.11 Eclipse Adoptium (64-bit), Linux x64

Also reproduced under tasks 3.4.3 / SonarScanner for MSBuild 10.3 — identical file set. This is
not a regression in either scanner version, which is why we believe it sits in the analyzer.

Languages of the repository

Analysed quality profiles in the affected run: cs, css, docker, js, json, powershell,
py, shell, ts, web, xml, yaml. The failing files are Azure Pipelines YAML, handled by
the iac sensors (IaC Azure Pipelines Sensor, IaC YAML File Type Classification Sensor).

Project URL

The project is private, so there is no public URL. No PR decoration involved — this reproduces on
both branch and PR analyses.

Error observed

All three are logged as WARN: Cannot parse '<file>' at default verbosity. The underlying message
appears only at DEBUG, and there is no Caused by: clause
— it is emitted as its own DEBUG
line immediately before the ParseException stack, with the exception class name inside the
message text. That is why this went undiagnosed on our side for months.

Signature 1 — CommentEvent cannot be cast to NodeEvent (4 files)

WARN: Cannot parse '<file>'
DEBUG: class org.snakeyaml.engine.v2.events.CommentEvent cannot be cast to class org.snakeyaml.engine.v2.events.NodeEvent (org.snakeyaml.engine.v2.events.CommentEvent and org.snakeyaml.engine.v2.events.NodeEvent are in unnamed module of loader org.sonar.classloader.ClassRealm @7b4acdc2)
DEBUG: org.sonar.iac.common.extension.ParseException: Cannot parse '<file>'
	at org.sonar.iac.common.extension.ParseException.createGeneralParseException(ParseException.java:40)
	at org.sonar.iac.common.extension.ParseException.createGeneralParseException(ParseException.java:35)
	at org.sonar.iac.common.extension.ParseException.toParseException(ParseException.java:63)
	at org.sonar.iac.common.extension.analyzer.AbstractAnalyzer.parse(AbstractAnalyzer.java:80)
	at org.sonar.iac.common.extension.analyzer.SingleFileAnalyzer.lambda$analyseFile$0(SingleFileAnalyzer.java:75)
	at org.sonar.iac.common.extension.DurationStatistics.time(DurationStatistics.java:72)
	at org.sonar.iac.common.extension.analyzer.SingleFileAnalyzer.analyseFile(SingleFileAnalyzer.java:75)
	at org.sonar.iac.common.extension.analyzer.SingleFileAnalyzer.analyseFiles(SingleFileAnalyzer.java:59)
	at org.sonar.iac.common.extension.IacSensor.executeIfActive(IacSensor.java:98)
	at org.sonar.iac.common.extension.TogglableSensor.execute(TogglableSensor.java:29)
	at org.sonar.scanner.sensor.AbstractSensorWrapper.analyse(AbstractSensorWrapper.java:67)
	at org.sonar.scanner.sensor.AbstractSensorsExecutor.executeSensors(AbstractSensorsExecutor.java:40)
	at org.sonar.scanner.sensor.ModuleSensorsExecutor.lambda$execute$1(ModuleSensorsExecutor.java:44)
	at org.sonar.scanner.sensor.ModuleSensorsExecutor.withModuleStrategy(ModuleSensorsExecutor.java:62)

Four Azure Pipelines templates, 245–692 lines each. The attached file is one of them.

Signature 2 — off-by-one, Range [1025, 1025 + 1) out of bounds for length 1025 (1 file)

WARN: Cannot parse '<file>'
DEBUG: Range [1025, 1025 + 1) out of bounds for length 1025
DEBUG: org.sonar.iac.common.extension.ParseException: Cannot parse '<file>'
	at org.sonar.iac.common.extension.ParseException.createGeneralParseException(ParseException.java:40)
	... identical stack to Signature 1

1025 = 1024 + 1 looks like an internal buffer boundary rather than our content: that file had no
scalar, comment run or line anywhere near 1024 characters (longest line, 251).

Signature 3 — ParseException with no underlying message at all (1 file)

WARN: Cannot parse '<file>'
DEBUG: org.sonar.iac.common.extension.ParseException: Cannot parse '<file>'
	at org.sonar.iac.common.extension.ParseException.createGeneralParseException(ParseException.java:40)
	... identical stack to Signature 1

No message line is emitted at all. Independently of the parse failure, we think this is a defect on
its own: it makes the whole class unreportable, because there is nothing to quote.

Common frame

All three bottom out at:

org.sonar.iac.common.extension.analyzer.AbstractAnalyzer.parse(AbstractAnalyzer.java:80)

Possibly related

The same files log this shortly before failing. We do not know whether embedded-script parsing and
the YAML event stream interact:

DEBUG: Failed to parse embedded bash content in block "stages.jobs.steps.inputs.inlineScript"

Steps to reproduce

  1. Add the attached service-dependencies-redacted.yml to a repository analysed by SonarQube Cloud,
    somewhere inside sonar.sources / sonar.inclusions.
  2. Run an analysis with the azurepipelines / iac sensors active (default).
  3. Observe WARN: Cannot parse '<path>' for that file, and note it produces no findings and no
    measures
    .
  4. Re-run with sonar.verbose=true to see the DEBUG cause line and the stack above.

The attached file reproduces Signature 1 deterministically — every run, both scanner majors. It is a
redacted copy of a real file from our repository (identifiers scrambled, byte length preserved); we
re-ran analysis against the redacted copy specifically to confirm the redaction did not mask the bug.

What we already ruled out

Please don’t spend time on these — we tested each:

  • snakeyaml #1062 (CommentEvent cast on a flow map with a comment after {). Does not
    apply: grepping [\{\[]\s*# across all 60 YAML files in the tree returns exactly one hit, and it
    is inside a PowerShell string in a file failing with a different signature.
  • Comment position generally. We scored every comment-attachment shape against all 60 files; the
    best predicate still had 42 false positives. There is no comment to “move”.
  • Azure DevOps template expressions (${{ }}). A passing file has 94 of them, 42 at key
    position; a failing file has none.
  • File size, line length, emoji, BOM, tabs, CRLF. The passing files lead on every one of those
    metrics.
  • Malformed YAML. All of them parse cleanly under PyYAML (1.1), eemeli/yaml
    (1.2 strict) and js-yaml.
  • Scanner/task version. Identical failures on tasks 3.4.3 (scanner 10.3) and 4.2.5
    (scanner 11.2.1).

Potential workaround

None that we are willing to take, and we would value your view on this.

Adding the files to sonar.exclusions changes nothing about findings — a file that fails to parse
already produces none. What exclusion does remove is the file from
EnterpriseTextAndSecretsSensor, which reads these same files successfully and is the
highest-value check we point at pipeline YAML. So exclusion is a net loss.

Rewriting the files is not viable either: we cannot verify a speculative rewrite without a full
pipeline run each time, and these files drive our release process. With no predicate that separates
passing from failing files, a rewrite would be guesswork.

So we have left them in scope and accepted a blind spot on five files.

What we would like

  1. Confirmation these are analyzer defects rather than something we can fix in our YAML.

  2. For Signature 3, the underlying exception surfaced in the log even when its message is null —
    independent of whatever causes the parse failure.

    sonar-iac-repro-service-dependencies.zip (2.0 KB)

Hi @pallib. Welcome to the community.

I’m able to replicate the parse error with the following.

variables:
  - name: special_services_registry
    value: |
      {}
  # this causes

  # parse error
  - name: global_change_patterns
    value: |
      {}

But, when I remove the empty line between the comments, I no longer get the parse error.

variables:
  - name: special_services_registry
    value: |
      {}
  # doesn't cause
  # parse error
  - name: global_change_patterns
    value: |
      {}

Both are valid YAML according to online YAML validators, so this could be a defect. I’m going to flag this post of our engineering team to investigate further.

Can you work around this for the time being?

Thanks Austin — confirming your workaround holds on real files, with measurements.

We removed the blank line from every comment/blank/comment run in the five
failing pipeline files: 13 blank lines, no other change. Verified inert by
composing each file to a YAML node graph before and after and requiring them
to be identical.

Isolated first on a single file so the others acted as controls in the same
scanner log — one file dropped out of “Cannot parse” while the other four
stayed. Then applied to all five. Full-repository scans before and after
(4,556 files, sonar-iac 2.14.0.22356):

before: 26 “Cannot parse” entries
after: 21
difference: exactly the five files, no new failures

Two things that may help your engineering team:

  1. Signature 3 is the same defect. sonarcloud-scheduled.yml threw a
    ParseException with no message and no cause — I reported it separately
    because there was nothing to match on. The comment-gap fix clears it too,
    so it looks like the same crash with the cause swallowed rather than a
    distinct bug.

  2. The shape is necessary but not sufficient. All five failing files
    contained it, but so did 8 files that parse fine — so something
    position-dependent gates it. Supporting evidence: generate-release-notes.yml
    (signature 2, the off-by-one) stopped failing on its own after unrelated
    edits shifted its byte offsets, with no comment change at all. Signature 2
    therefore remains unexplained — it may or may not be the same root cause.

We’ve taken this as a temporary workaround, not a fix. It is held only by
convention: nothing stops the pattern reappearing in a new file, and the
8 files that share the shape today could start failing if their offsets move.
Still glad to have it — those five files had never been analysed.

Happy to supply a sonar.verbose log, the redacted reproducer, or a second
failing file if any of that helps.

Palli

Hi @pallib

Thank you for the report. I looked into your reproducer and after removing blank lines between 2 comments all parsing issues are gone, as confirmed by @austin.smith. It is old known limitation of the snakeyaml-engine - a Java library for parsing YAML we are using in sonar-iac.
Unfortunately without the full reproducer I’m not able to investigate the other issues. Let me open a private conversation where you can share with me a full reproducer.

Best
Marcin Stachniuk

The other issue related to log:

DEBUG: Range [1025, 1025 + 1) out of bounds for length 1025

was this one: #82 - StreamReader.update() throws IndexOutOfBoundsException when a full buffer read ends on a high surrogate - snakeyaml/snakeyaml-engine - Codeberg.org in library we are using for parsing YAML. We updated this dependency and it is already available in SonarQube Cloud and it will be available with the next SonarQube Server release (2026.6 and community build 26.9).

Best
Marcin Stachniuk