Fluent CI/CD Pipelines
How to run a ServiceNow Fluent (SDK 4.x, now-sdk CLI) project through version control and CI: what to commit, the PR check that catches stale keys.ts, headless auth, a minimal GitHub Actions workflow, and the hard line between CI deploys and production promotion. For the official reference, run snow_fluent_explain with topic ci-integration (offline now-sdk explain ci-integration).
Where these tools run: the
snow_fluent_*tools are local-only — they execute the ServiceNow SDK (now-sdk) on the developer's machine, so they only exist when the MCP server runs locally over stdio. Over a hosted HTTP transport (e.g. a web chat) they are not available, so do not call them; drive the same flow through git + CI instead: commit the project files, trigger the build/deploy pipeline, and follow the workflow runs.
1. Repo discipline
The .gitignore that snow_fluent_init scaffolds is already correct — keep it as-is:
gitignore.DS_Store .now/ dist/ node_modules/ target/ *.tsbuildinfo .jest_cache
dist/ and .now/ are build outputs; never commit them.
src/fluent/generated/keys.ts MUST be committed. It is the registry mapping every Now.ID['...'] to a sys_id — the record-identity source of truth. It is regenerated by every build. If a teammate's CI or machine builds without your committed keys, records get NEW sys_ids and install creates duplicates on the instance.
text❌ Add keys.ts to .gitignore ("it's generated, so ignore it") → duplicate records on the next install from another machine ✅ Commit keys.ts on every change, enforce with --frozenKeys in PR checks (section 2)
One app per repo is the simplest layout and what these examples assume. The SDK itself doesn't mandate a layout — projects can be arranged freely as long as now.config.json is set up correctly. now.config.json binds the project to one scope (scope/scopeId/name; optionally tsconfigPath) and contains no instance connection info, so it is safe to commit.
2. The PR check: build with frozen keys
Every pull request should run:
bashnpm ci npx now-sdk build --frozenKeys
--frozenKeys makes the build FAIL if compiling the Fluent code would change keys.ts. The exact failure message:
textKeys file is out-of-date. To update it, run the build again without frozen keys.
What that failure means: a developer added or renamed a Now.ID and forgot to commit the regenerated keys.ts. The fix is on the PR author's machine, not in CI:
bashnow-sdk build # regenerates keys.ts git add src/fluent/generated/keys.ts && git commit
Locally, the same gate is snow_fluent_build with frozen_keys=true. Add error_on_conflict=true (--errorOnConflict) on brownfield projects so a record that exists both as Fluent code and as XML in metadata/ fails the build instead of silently shipping the XML version.
ES5 note: Fluent DSL files (*.now.ts) are modern TypeScript — const, arrow functions, template literals are all correct there, and CI compiles them with the SDK's own toolchain. The ES5-only rule applies ONLY to script content that executes on the instance's Rhino engine, i.e. the string you put in a script: property (Business Rule bodies, Script Includes, etc.). A --frozenKeys build will not catch ES6 inside a script: string; review those by hand or with the code-review skill.
3. Headless auth (env vars, no keychain)
now-sdk auth --add is interactive (inquirer prompts, no --username/--password flags) — it cannot run in CI. Instead set env vars, which "take precedence over stored credentials" and bypass the OS keychain entirely:
bash# Always required to enable CI mode: SN_SDK_NODE_ENV=SN_SDK_CI_INSTALL # Option A — basic auth (default if SN_SDK_AUTH_TYPE unset): SN_SDK_AUTH_TYPE=basic SN_SDK_INSTANCE_URL=https://yourtest.service-now.com SN_SDK_USER=ci.user SN_SDK_USER_PWD=******** # Option B — OAuth client_credentials (SDK 4.7.0+, token fetched per run from ${SN_SDK_INSTANCE_URL}/oauth_token.do): SN_SDK_AUTH_TYPE=oauth SN_SDK_INSTANCE_URL=https://yourtest.service-now.com SN_SDK_OAUTH_CLIENT_ID=... SN_SDK_OAUTH_CLIENT_SECRET=...
Instance-side prerequisites for Option B (do these once on the target instance):
- Set sys_property
glide.oauth.inbound.client.credential.grant_type.enabled=true. - Application Registry entry of type "OAuth API endpoint for external clients", Public Client = false, Grant type includes Client Credentials.
- Map an OAuth Application User that has install roles (typically admin) AND Identity Type = Human — Machine identities are blocked during the SDK's session-token handshake.
text❌ Run `now-sdk auth --add` in a CI step → hangs on interactive prompts ❌ OAuth app user with Identity Type = Machine → install fails at the CSRF/session step ✅ SN_SDK_NODE_ENV=SN_SDK_CI_INSTALL + SN_SDK_* secrets from the CI secret store
4. Minimal GitHub Actions workflow
There is no official GitHub Action for the Fluent SDK (the marketplace "ServiceNow CI/CD" actions are the classic App-Repo flow). Official guidance is plain now-sdk steps. Node 20+ is required (engines: node >=20.18.0).
yamlname: fluent-ci on: pull_request: push: branches: [main] jobs: verify: if: github.event_name == 'pull_request' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - run: npm ci - name: Verify keys.ts is up to date run: npx now-sdk build --frozenKeys deploy-test: if: github.event_name == 'push' && github.ref == 'refs/heads/main' runs-on: ubuntu-latest env: SN_SDK_NODE_ENV: SN_SDK_CI_INSTALL SN_SDK_AUTH_TYPE: oauth SN_SDK_INSTANCE_URL: ${{ secrets.SN_TEST_INSTANCE_URL }} SN_SDK_OAUTH_CLIENT_ID: ${{ secrets.SN_TEST_OAUTH_CLIENT_ID }} SN_SDK_OAUTH_CLIENT_SECRET: ${{ secrets.SN_TEST_OAUTH_CLIENT_SECRET }} steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - run: npm ci - run: npx now-sdk build --frozenKeys - name: Install to TEST instance run: npx now-sdk install
Tuning the install step: --demoData defaults to TRUE (pass nothing to keep it; relevant when your app ships demo records), --skip-flow-activation disables the automatic flow publish that install does since SDK 4.5.
5. Production: NEVER install from CI
Official SDK guidance, verbatim: "Do not use now-sdk install from CI to deploy to production instances." The promotion path is git plus the Application Repository: publish the app from the test instance to the App Repo and install it in production from there — that is where change management and rollback live.
Why this is a hard rule: now-sdk install bypasses update sets ("Installing via now-sdk does not generate changes in update sets as of now") and "there is no rollback context created for app installed via now-sdk". The only SDK-side undo is --reinstall (uninstall + fresh install — destructive).
text❌ main-branch job that runs now-sdk install against prod ✅ CI installs to a shared TEST instance → publish from that instance to the Application Repository → install in test/prod via the App Repo (change management + rollback live there)
6. Exit-code and output gotchas
- Fluent compile errors → exit 1 with
ERROR: Build failed due to errors. Good, fails the job. - Missing/unfindable
package.json→[now-sdk] ERROR: Could not find package.json...but exit code 0. A misconfiguredworking-directorymakes the build step "pass" while building nothing. Guard it:
yaml- name: Build run: | npx now-sdk build --frozenKeys test -d dist/app # fail the job if nothing was actually built
- There is no JSON output mode anywhere in the CLI; output is human log lines prefixed
[now-sdk]. Grep stdout/stderr if you need to assert on messages. - Locally, mirror the pipeline before pushing:
snow_fluent_statusto confirm scope/SDK pin/keys.ts state,snow_fluent_buildwithfrozen_keys=truefor the PR gate,snow_fluent_installfor the test-instance deploy.

