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:
.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.
❌ 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:
npm ci
npx now-sdk build --frozenKeys
--frozenKeys makes the build FAIL if compiling the Fluent code would change keys.ts. The exact failure message:
Keys 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:
now-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:
# 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.
❌ 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).
name: 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).
❌ 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:
- 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.