AI agent skill · by @serac-labs
Fluent Ci Cd
This skill should be used when the user asks to "set up ci for fluent", "github actions for servicenow", "deploy fluent from a pipeline", "fluent pull request checks", "automate now-sdk", or reports "keys.ts out of date" — anything about running the ServiceNow SDK (now-sdk) headless in CI/CD.
Archived 2026-10-23 · Source updated 2026-09-10 · License: Apache-2.0. Check the original repository for the latest version.
SKILL.md instructions
# 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:
```bash
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:
```text
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:
```bash
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:
```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):
1. Set sys_property `glide.oauth.inbound.client.credential.grant_type.enabled` = `true`.
2. Application Registry entry of type "OAuth API endpoint for external clients", Public Client = false, Grant type includes Client Credentials.
3. 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`).
```yaml
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).
```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 misconfigured `working-directory` makes 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_status` to confirm scope/SDK pin/keys.ts state, `snow_fluent_build` with `frozen_keys=true` for the PR gate, `snow_fluent_install` for the test-instance deploy.