Introduction
CI/CD pipelines are designed to provide confidence. When a pipeline fails, the assumption is that something is genuinely wrong - a test broke, a dependency failed, or a configuration is missing.
Occasionally, however, pipelines fail in ways that don’t immediately make sense.
In our case, SonarCloud began failing in a GitHub Actions CI pipeline even though Node.js was installed, all tests were passing, and the pipeline configuration appeared correct. At first glance, the failure looked like a tooling issue. In reality, it turned out to be a subtle runtime-ordering problem - the kind that’s easy to overlook and difficult to diagnose.
This article walks through what happened, why the issue was misleading, and how we fixed it cleanly without introducing hacks or brittle workarounds.
What is SonarCloud and Why We Use It
SonarCloud is a static code analysis service that continuously inspects code for bugs, security vulnerabilities, and maintainability issues. In our organisation, SonarCloud acts as a quality gate in the CI pipeline, helping ensure that new changes meet agreed-upon code quality standards before they are merged.
For JavaScript and TypeScript projects, SonarCloud performs deep source-code analysis using a JavaScript engine that depends on Node.js. This means that, even though SonarCloud is not building or running the application itself, it still requires a compatible Node.js runtime to analyse the code correctly.
In our CI setup, SonarCloud runs automatically as part of GitHub Actions on pull requests and feature branches. Any failure at this stage blocks the pipeline, making SonarCloud a critical part of our development workflow.
This dependency on Node.js - and when that runtime is active - is the key to understanding the issue we encountered.
CI/CD Setup Context
The issue occurred in a GitHub Actions–based CI pipeline used for a JavaScript/TypeScript application. The pipeline was designed to be straightforward and intentionally modular, with clear separation between development, testing, and quality checks.
At a high level, the CI workflow included:
- Code checkout
- Dependency installation using pnpm
- Linting
- Multiple test suites
- SonarCloud analysis as a quality gate
The project used Node.js 18 as the primary runtime for application development and testing. All test commands were explicitly run using this version, and they completed successfully on every execution. From an application perspective, the pipeline was healthy.
SonarCloud was configured to run after tests, analysing the same source code that had just passed validation. The expectation was simple: if the code compiled and tests passed under Node.js 18, static analysis should also succeed.
Importantly, this pipeline existed in the application repository itself. Infrastructure and deployment were handled in a separate repository, where this application was included as a Git submodule. SonarCloud analysis was intentionally scoped only to the application repository to keep CI responsibilities clearly separated.
From a configuration standpoint, everything appeared correct:
- Node.js was installed
- The correct version was specified
- SonarCloud was integrated using the official GitHub Action
Yet despite this, the SonarCloud step failed consistently. That contradiction - a clean pipeline up until the quality gate - is what made this issue both confusing and time-consuming to debug.
The Problem and Error Symptoms
The failure occurred only during the SonarCloud analysis step. Every stage before it - dependency installation, linting, and all test suites - completed successfully.
SonarCloud failed with errors related to its JavaScript analysis engine, including messages suggesting:
- Unsupported JavaScript features
- Failure to start the internal analysis process
- Requirement for a supported Node.js version
This was confusing because Node.js was clearly installed and working. The pipeline explicitly set up Node.js, node -v returned a valid version, and the same runtime had just executed all tests without issue.
From a debugging perspective, this created a contradiction:
- If Node.js were missing or broken, tests should have failed.
- If the Node version were incompatible, the pipeline should have failed earlier.
Instead, the failure surfaced only at the SonarCloud stage, with error messages pointing to Node.js but not clearly explaining the underlying problem. This ambiguity made it difficult to immediately determine whether the issue was related to SonarCloud, GitHub Actions, or the CI configuration.
Initial Assumptions and Why They Were Wrong
Given the error messages, the initial assumption was that the Node.js setup was incorrect. This was a reasonable conclusion - SonarCloud explicitly reported problems related to the Node runtime.
The first checks focused on confirming that Node.js was installed correctly:
- Verifying the Node version in the pipeline
- Ensuring Node was available in the PATH
- Confirming that tests were running with the expected runtime
All of these checks passed.
The next assumption was that SonarCloud itself might be misconfigured. Various common fixes were considered, including reinstalling Node.js, forcing a specific executable path, or adding additional environment variables to guide the scanner.
While these approaches seemed promising, they were ultimately addressing symptoms rather than the root cause. Node.js was not missing, and SonarCloud was not misconfigured in an obvious way.
The real problem was not whether Node.js was installed, but which version of Node.js was active at the exact moment SonarCloud started its analysis. This distinction was easy to overlook and was not immediately obvious from the error messages.
Root Cause Summary
The issue was caused by the order in which the CI pipeline steps were executed. Although Node.js 18 was installed and used successfully for running tests, the SonarCloud GitHub Action was triggered before the intended Node.js runtime was fully applied to the runner environment.
As a result, SonarCloud defaulted to a different Node.js version that lacked support for certain modern JavaScript features.
This mismatch led to misleading errors during the analysis phase, even though the application itself was functioning correctly under the expected runtime.
How We Fixed the Issue
To resolve the problem cleanly and ensure consistent runtime behaviour across the pipeline, we made some adjustments:
Instead of adding overrides, hardcoding paths, or introducing custom scanner settings, we only needed to ensure that the required Node.js version was active immediately before SonarCloud started.
In practice, this meant keeping Node.js 18 for dependency installation and test execution, and then switching to Node.js 20 just before running the SonarCloud scan.
By doing this, we allowed each stage of the pipeline to use the runtime it expected, without interfering with other steps.
Once these changes were applied, SonarCloud analysis ran successfully, and the CI pipeline stabilised.
Key Lessons Learned
- CI tools depend on the active runtime environment, not only on what is installed in the pipeline.
- The order of execution can significantly change how tools behave.
- Verifying the runtime in use at each stage is just as important as validating the configuration itself.
- When failures appear to be code-related, the root cause may actually be environmental.

No comments:
Post a Comment