> For the complete documentation index, see [llms.txt](https://docs.sealsecurity.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.sealsecurity.io/setup-apps-os/cli-in-cicd/common-issues.md).

# Common issues

The Seal CLI fails the same way in CI as it does on a developer's machine. A few specific issues come up disproportionately often in pipeline contexts.

## Network issues

**The CLI cannot reach Seal's servers.** The runner needs HTTPS access to the Seal Platform and the [Seal Artifact Server](/discovering/artifact-server-discovery.md). The most common blockers:

* A corporate firewall or egress proxy is filtering outbound HTTPS by hostname. Allow-list Seal's hostnames at the egress point.
* A zero-trust network product such as **ZScaler**, **Netskope**, or similar is intercepting outbound traffic and rejecting Seal's servers, or rewriting the connection in a way the CLI cannot complete. Add Seal's hostnames to the product's allow-list, or run the CLI on a runner that is not behind the inspection layer.
* The CI runner is in a private subnet without outbound internet. Either give the runner egress, or move to the on-prem workflow described in [On-prem and air-gapped environments](/setup-apps-os/on-prem.md).

For fully air-gapped environments, see the same chapter for the air-gapped variants.

## Authentication errors

**`SEAL_TOKEN` is not set** or has been revoked. Confirm the variable is exported in the step's environment, that the token is a current value (not a rotated one), and that the token is the right type for the activity (production for production-equivalent CI, development for developer machines and feature-branch CI).

If the CI step is being run by a forked pull request, your CI platform may not expose secrets to runners by default; the runner sees `SEAL_TOKEN` as empty even though it is set at the project level. Check your platform's fork-PR secrets handling.

## CLI misconfiguration

**The wrong fix mode**. `seal fix --mode remote` fetches rules from the Seal Platform; `seal fix --mode local` reads them from `.seal-actions.yml`; `seal fix --mode all` ignores both and seals every vulnerable package. Mixing them up (for example, running in `local` mode while your rules live in the platform UI) results in no rules being applied. Match the mode to where your rules actually live.

**The wrong `SEAL_PROJECT`**. Two unrelated CI pipelines running with the same `SEAL_PROJECT` clobber each other's results in Seal's view. See the [Anti-patterns section in Creating a Seal Project](/getting-started/creating-a-seal-project.md#anti-patterns).

**The manifest is not auto-detected**. The CLI looks in the working directory by default. If your manifest is elsewhere, pass it with `--manifest <path>` on the CLI invocation, or `cd` into the directory before running. Common case: a monorepo where the relevant lockfile is several directories deep.

**The wrong manifest is scanned**. The CLI parses the file it finds first. For npm, `package-lock.json` is the right input; `package.json` does not pin versions and is rejected. For Python, both `requirements.txt` and `Pipfile.lock` are valid. If the CLI scans the wrong one, point at the right one with `--manifest`.

## Step placement

**The CLI runs before the dependency-install step**. A common mistake is to put `seal fix` before `npm install` / `mvn install` / `pip install`. The CLI replaces packages on disk that the install step has already put there; if no packages are installed yet, there is nothing to replace and the seal step is effectively a no-op. Run `seal fix` **after** the install step.

**The CLI runs after the packaging step**. The mirror image of the above. If `seal fix` runs after the step that bundles or compiles the dependencies into your final artifact (webpack, JAR packaging, `docker build`, and so on), the packaged output is already built from the vulnerable versions. Run `seal fix` **before** the packaging step.

**The CLI runs on the wrong commit**. In branch-based CI, the CLI runs against whatever is checked out. If your workflow checks out a different ref before the CLI step, the scan and the build can diverge.

## Sealing Rules apply but the build still uses vulnerable versions

A common report. The usual causes:

* **You are looking at an artifact the current build did not produce.** A binary that was uploaded earlier, or built in a previous iteration of the pipeline, will still carry whatever was in it at build time. Confirm the artifact under inspection is the one this build wrote.
* **The Sealing Rule does not match the package**. The rule targets a specific origin version; if your manifest pins a slightly different version, or the rule was created in a different Seal Project, it does not apply to this run.
* **The CLI fix mode does not match where the rules are stored**. `local` reads `.seal-actions.yml` and ignores the platform UI; `remote` does the opposite. A remote-stored rule plus a `local` fix-mode call is a no-op.
* **A cache is serving stale artifacts**. The runner's package-manager cache, or an internal artifact-server cache between the runner and Seal, can return a previously-pulled vulnerable version instead of the sealed one. This is most common in Java (older Maven and Gradle configurations cache aggressively). Clear the cache and re-run.
* **The vulnerability is not one Seal remediates**. Not every CVE has a sealed counterpart. See [Partial fixes & open vulnerabilities](/discovering/partial-fixes.md) for the reasons.

## Platform-specific gotchas

**GitHub Actions**: secrets are not exposed to runners triggered by pull requests from forks. The CLI step in those PR runs sees `SEAL_TOKEN` as empty. Either run the seal step on `push` triggers only, or configure your repository's fork-PR secrets handling explicitly.

**Jenkins**: the `credentials()` binding sets the environment variable only inside the step that declares it. If your Jenkinsfile splits the install and the run into separate stages, both need the binding.

**CircleCI**: project-level environment variables and context-level variables behave the same in normal jobs but interact differently with restricted contexts. If a job that worked locally fails in the protected context, confirm the token is visible there.

**Azure DevOps**: pipeline variables marked secret are not exposed to the shell environment by default. The script step has to explicitly `export SEAL_TOKEN=$(SEAL_TOKEN)` to make the value visible to the CLI.

**Docker (any CI)**: when running the CLI inside a Dockerfile via `RUN`, BuildKit caching can keep the CLI step's output from re-running when you expect it to. Either invalidate the cache (touch the dependency file before the CLI step, or pass `--no-cache` to `docker build`) or split the CLI step into a stage whose output you do not cache.

## Related

* [The CLI fix mode](/setup-apps-os/cli-in-cicd/cli-fix-mode.md): the right `--mode` for each deployment method.
* [Connecting via the Seal CLI in CI](/discovering/cli-in-ci.md): the discovery-side basics, with parallel troubleshooting.
* [Partial fixes & open vulnerabilities](/discovering/partial-fixes.md): the reasons a sealed package may still carry open vulnerabilities.
