Blog

netsuite · suitecloud · sdf

SuiteCloud CLI 4.0 Validation in CI/CD: What Changed and How to Fix Your Pipeline

SuiteCloud CLI 4.0 drops local validation, ignores --server and --validate, and changes its output. Here is what breaks in a CI/CD pipeline and how to fix it.

MokuHub9 min read

The pipeline stays green and the warning is the only clue

Say your deploy job installs @oracle/suitecloud-cli without a pinned version. One morning the log for the validate step carries a line nobody wrote:

The "--server" option has no effect. Project validation is always performed on the server.

A few lines down, the deploy step says the same thing about the other flag:

The "--validate" option has no effect. Project deployment is always validated on the server.

The job does not fail. The CLI logs the warning and keeps going. What fails is whatever downstream read the log. The step that pulled a deployment summary into a chat notification now posts something nobody recognizes, and the check that searched for a specific line before allowing the next stage stops finding it. Both were written against an output format that no longer exists.

This is SuiteCloud CLI for Node.js 4.0.0, released on GitHub on August 19, 2026 [1]. The 2026.2 release notes describe the change in one paragraph and end it with an instruction: review your CI/CD pipelines [2].

Here is the pipeline after that review, then the reasons for each line.

# .github/workflows/sdf-deploy.yml (GitHub Actions shown, the steps carry over)
jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      SUITECLOUD_CLI_ACCEPT_LICENSE: "true"
      SUITECLOUD_CI: "1"
      SUITECLOUD_CI_PASSKEY: ${{ secrets.SUITECLOUD_CI_PASSKEY }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: "21"
      - uses: actions/setup-node@v4
        with:
          node-version: "24"
      - run: npm install -g @oracle/suitecloud-cli@4.0.0
      - name: Restore CI credentials
        run: |
          mkdir -p ~/.suitecloud-sdk
          echo "${{ secrets.SUITECLOUD_CREDENTIALS_P12 }}" | base64 -d > ~/.suitecloud-sdk/credentials_ci.p12
      - run: npm ci
      - run: npx jest
      - run: suitecloud project:validate --json > validate.json
      - run: suitecloud project:deploy --dryrun
      - run: suitecloud project:deploy --json > deploy.json

Neither old flag appears, and no step reads the human output. Each run line passes or fails on the process exit code alone.

Local validation is gone, so every validate is a call to an account

Before 4.0, project:validate had two modes. With --server, the project was validated by NetSuite. Without it, validation ran on your machine. The old Java CLI documentation shows what the local run printed: "Validating using client-side SDF metadata" [3]. On the deploy side, --validate ran a validation pass before deploying and stopped the process if it found an error.

The release notes for 4.0.0 put it plainly: local validation has been removed, validation occurs on the server by default, and project:validate --server and project:deploy --validate have no effect [2]. The current project:validate reference no longer lists --server at all. Its syntax is now:

suitecloud project:validate [--applyinstallprefs] [--accountspecificvalues (WARNING|ERROR)]

[4]

The flags were not deleted from the parser, which is why old commands still run. In the 4.0.0 source, passing either one triggers the warning message and the command continues with the flag ignored [5].

The practical consequence is about where validation happens, not how. Server-side validation checks the project against the account it is pointed at, including feature and file dependencies [6]. So the validate step in your pipeline now needs working credentials for a real account, and its result depends on the state of that account. A project that validates against your sandbox can fail against production if production is missing a feature the project depends on. That is the correct behavior, and it is also a reason to validate against the same account you are about to deploy to.

If your pipeline used local validation as a cheap first gate on every pull request, with no account access at all, that gate has no direct replacement in the CLI. The offline check that remains is unit testing. Oracle ships SuiteCloud Unit Testing for Jest [7], with SuiteScript 2.x stubs for the SuiteScript modules [1], so it runs on the build machine without an account. That is why npx jest sits before the first suitecloud call in the pipeline above. It is not the same check. It catches broken logic in your scripts, not a missing custom field in an object XML file.

Gate on the exit code and never on the text

This is the one rule I would not bend on.

Oracle's own instruction for 4.0 is to update scripts that read or parse command output, and to not rely on the existing format, including progress messages, validation results, deployment summaries, warnings or errors [2]. The new human-readable output has a "Status:" line, a step counter and a count of errors and warnings [4]. It is tempting to grep for Status: SUCCESS and call the migration done. That rebuilds the exact dependency that just broke, against a format Oracle has already told you not to depend on.

The exit code is the stable contract. In the 4.0.0 source, after every command the CLI sets the process exit code to 0 if the action succeeded and 1 if it did not [5]. Every CI system already stops a job on a nonzero exit. A step like this needs no parsing:

suitecloud project:validate

If you need data out of the run, for a notification or an artifact, use the JSON flag. Both project:validate and project:deploy accept --json (short form -j), which prints the raw JSON response from the server and skips the formatted output [5]. Write it to a file and keep the file:

suitecloud project:deploy --json > deploy.json

One honest limit here. Oracle has not published a schema for that JSON. I could not find field names in the help center or the release notes, so I am not going to list any. Before you build a notifier on top of it, run it once against a sandbox, look at what comes back, and treat the field names as something that can change between versions. The exit code decides whether the job passed. The JSON is only for describing what happened.

The account behind the pipeline needs machine-to-machine auth

Since validation now always reaches an account, the pipeline needs credentials it can use without a browser. The CLI supports this through machine-to-machine authentication.

First, in NetSuite, upload a certificate in OAuth 2.0 Client Credentials Setup, mapped to the application "SuiteCloud Development Integration" [9]. Then, on your own machine with SUITECLOUD_CI_PASSKEY set, run account:setup:ci with the account ID, an auth ID, the certificate ID and the path to the private key. It needs no browser login. The command writes an encrypted credentials file, credentials_ci.p12, under ~/.suitecloud-sdk, and that file goes into the home directory of the user that runs the pipeline [8]. Setting up the auth ID needs only the passkey; the pipeline itself needs both SUITECLOUD_CI_PASSKEY and SUITECLOUD_CI [10].

export SUITECLOUD_CI_PASSKEY='<32 to 100 alphanumeric characters>'
suitecloud account:setup:ci \
  --account 1234567_SB1 \
  --authid sandbox-ci \
  --certificateid <certificate ID from NetSuite> \
  --privatekeypath ./private-key.pem

The passkey must be 32 to 100 alphanumeric characters, and changing it invalidates the existing credentials file, so you rerun the setup [10]. Storing the file as a base64 secret and decoding it at job start, as in the pipeline above, is one way to get it onto a hosted runner. It is my choice, not something Oracle prescribes.

Two details catch people. You can't switch the default auth ID through the CLI inside the pipeline; set defaultAuthId in project.json instead [10]. And if you already have auth IDs on your machine, account:setup:ci --select myAuthId reuses one rather than taking every parameter again, an option added in CLI 3.1.0 [1].

If the pipeline deploys to production, the cleanest arrangement is one auth ID per target account and the defaultAuthId set per branch or per job. The full path from sandbox to production, including what SDF does and does not move for you, is covered in our sandbox to production migration guide.

The runner needs Node 24 and a license variable before the first command

Two install-time changes break pipelines before validation even starts.

CLI 4.0.0 requires Node.js 24 LTS [1]. The prerequisites page lists Node.js 24 LTS together with JDK 17 or 21, the SuiteCloud Development Framework feature enabled in the account, and OAuth 2.0 enabled [11]. A runner image that still carries an older Node will fail at install, not at validate.

The install also shows a license prompt. In a script there is nobody to accept it. The README documents an environment variable for this case: setting SUITECLOUD_CLI_ACCEPT_LICENSE=true suppresses the prompt and confirms acceptance of the Oracle Free Use Terms and Conditions [5].

SUITECLOUD_CLI_ACCEPT_LICENSE=true npm install -g @oracle/suitecloud-cli@4.0.0

Pin the version. The supported versions page lists 4.0.0 and 3.2.0 as the versions currently supported on NPM [12]. An unpinned install is how the warning in the opening scenario showed up uninvited. If you need time to rework the pipeline, pinning 3.2.0 buys it while it stays on that list. Pinning 4.0.0 means you choose when 4.1.0 arrives, which added a config:import command on September 16, 2026 [1].

Dry runs still work, but their output changed too

Oracle names one command specifically in the 4.0 notes: deployment previews performed with project:deploy --dryrun [2]. The flag still exists in the current deploy reference and is only available in non-interactive mode [13].

If you had a step that ran a dry run and posted the preview to a pull request, that step parsed text, and it needs the same treatment as the rest. Keep the dry run as its own step so a failure is easy to locate in the log. Treat its output as something a human reads in the job log, not something a script extracts.

The same reference lists --accountspecificvalues on deploy with a default of ERROR. That means a project carrying account-specific values fails the deploy unless you explicitly pass WARNING [13]. If your pipeline deploys the same project to several accounts, that default is a feature. Leave it on.

Deploying SDF projects through a pipeline also means your source of truth is the repository, not the File Cabinet. If the team still edits scripts directly in the account, the pipeline will overwrite those edits on the next deploy. Our piece on File Cabinet version control covers how to get existing scripts into Git first.

Where this stops working

The JSON output has no published schema. Any code that reads fields from it is reading an undocumented contract.

I could not confirm what exit code project:validate returns when the server reports warnings but no errors. The source sets 1 for a failed action and 0 for a successful one, but whether a warnings-only result counts as success is not written down. Run it on a project with a known warning before relying on either answer.

Validation against a live account means a pipeline can fail because of the account, not the code. A feature disabled in the target account, or an object someone deleted by hand, will fail the validate step on a pull request that touched neither. Read the failure before reverting the change.

The IDE plugins changed in the same release. Oracle lists changed validation and deploy output for the VS Code extension 4.0.0 and the WebStorm plug-in 2026.2 [2]. Developers will see the new format locally as well, which is good for consistency and bad for any personal scripts built on the old one.

The Java CLI is out of this entirely. It has reached end of support, its final version is 2025.2, and it stays downloadable only until the NetSuite 2027.1 release [2]. A pipeline still built on it has a fixed deadline, and moving to the Node CLI now means taking this whole list on at once.

If you are migrating an SDF pipeline and want someone to look at it with you, write to sales@mokuhub.com.

Sources

  1. SuiteCloud SDK releases, GitHub: https://github.com/oracle/netsuite-suitecloud-sdk/releases
  2. SuiteCloud SDK, NetSuite 2026.2 release notes: https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_1558730192.html
  3. project:validate, SuiteCloud CLI for Java: https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_4788674371.html
  4. project:validate, SuiteCloud CLI for Node.js: https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_156049843194.html
  5. SuiteCloud SDK source, node-cli 4.0.0: https://github.com/oracle/netsuite-suitecloud-sdk
  6. SuiteCloud Project Validation: https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_4719750997.html
  7. SuiteCloud Unit Testing with Jest: https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_164882052792.html
  8. Machine-to-machine authentication for CI: https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/article_94164947835.html
  9. account:setup:ci: https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/article_81134826821.html
  10. CI environment variables and credentials file: https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/subsect_28104403712.html
  11. SuiteCloud CLI for Node.js prerequisites: https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_1558708810.html
  12. Supported versions in NPM: https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_158409687381.html
  13. project:deploy, SuiteCloud CLI for Node.js: https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_156044636320.html