Skip to content

The engine works out whether it is in CI, instead of asking every host - #155

Merged
wmadden-electric merged 1 commit into
mainfrom
engine-owns-ci
Aug 12, 2026
Merged

The engine works out whether it is in CI, instead of asking every host#155
wmadden-electric merged 1 commit into
mainfrom
engine-owns-ci

Conversation

@wmadden-electric

Copy link
Copy Markdown
Contributor

A host used to have to tell the engine whether it was running in CI:

import { isCI } from "ci-info";   // every host, separately

const runtime: Runtime = { /* … */ isCI };

It no longer does. The engine works it out from the environment the host already hands it:

const runtime: Runtime = { /* … */ };   // says nothing about CI

The decision

CI detection belongs to the engine. Runtime.isCI was required of every host, which meant every host imported the same package to compute the same boolean — and a host that wrote its own version instead forked a table of CI vendors that goes stale as new ones appear. That is what prompted this: composer, mounting its commands into this CLI, became the second host and had to answer the same question.

The engine's own rationale for pushing it out was that "the engine detects CI no more than it detects a TTY." The analogy does not hold. TTY-ness is not derivable from the environment; CI-ness is, and the engine already receives Runtime.env — the injected, process-global-free environment its own rules require it to read from.

The field survives as isCIOverride, optional. Removing it outright was tempting, but a test that needs to force not-CI would then have to doctor env.CI, and that variable is read by other things — the interactivity decision, the telemetry opt-outs. The rename matters as much as the optionality: left as isCI?: boolean it still reads like something a host ought to answer. undefined means detect, so the safety property that made it required in the first place still holds — a host that says nothing gets detection, never false.

How the detection avoids becoming the thing it replaces

ci-info has no exports map and ships vendors.json among its files, so the upstream vendor table is importable directly. The engine reads that table and matches it against the injected env; nothing about any individual CI provider is copied into this repo. What is ours is a twenty-line matcher, and ci-info's nine conventional variable names (CI, BUILD_ID, RUN_ID, …), which live in its index.js as code rather than data — cross-vendor conventions, not per-vendor facts.

The matcher was checked against reality rather than reasoned about: a throwaway script compared it to real ci-info in child processes across all 53 vendors in the table plus seven edge cases, with no disagreements.

Interactivity now shares it

The engine had a second, cruder CI test deciding whether to prompt: runtime.env.CI === undefined. It now uses the same detection, which is better in both directions:

  • Jenkins, TeamCity and Azure Pipelines set no CI variable at all. They were being offered prompts with nobody there to answer them.
  • CI=false is an explicit denial, and it used to cost a developer their prompts, because the old test only asked whether the variable was set.

Testing

Sixty-eight cases, built around what could go wrong rather than what the code does: a CI-looking process.env is ignored when the injected env is clean, and the reverse; five vendors in their own env shapes, including the ones that set no CI variable and can only be found through the table; Jenkins needing both of its variables present; CI=false outranking a vendor's own variables; and every vendor in the installed table detected from its own entry, so an upstream shape the matcher does not understand fails loudly rather than silently.

Through whole runs: a TeamCity or Azure environment that no host declared reports no telemetry, a developer's shell does, and the override beats detection either way.

Nine existing engine tests got shorter — they deleted the isCI: false line they used to need, which is the change stated in one diff.

🤖 Generated with Claude Code

Runtime.isCI was a required field every host had to answer, and every
host answered it the same way: import ci-info, pass its isCI through.
prisma-cli's own bin did exactly that, composer's CLI had to do it
again, and a host that hand-rolled the answer instead would fork a
vendor detection table that goes stale.

The old rationale was that "the engine detects CI no more than it
detects a TTY". That comparison does not hold. TTY-ness cannot be
derived from the environment; CI-ness can, and the engine is already
handed the environment as Runtime.env.

So the engine detects it. src/ci.ts reads ci-info's vendors.json — the
upstream table itself, imported from the installed package rather than
copied into this repo, so upgrading ci-info is what teaches the engine a
new provider — and matches it against the injected env. ci-info's own
isCI export cannot be used: that module evaluates against the real
process.env the moment it is imported, which the engine may not read.
Only the ~20-line matcher is ours. It agrees with ci-info's isCI on all
53 vendors in the current table and on the CI=false, empty-CI and
partial-vendor edge cases.

Runtime.isCI becomes Runtime.isCIOverride, optional. Absence means
DETECTED, never false, so the safety property the required field existed
for still holds: a host that says nothing stays silent in CI. The
override remains for a host detection cannot serve, and for tests that
need both sides of the branch.

Interactivity now shares that detection. It used to ask whether CI was
set to anything at all, which got two things wrong: Jenkins, TeamCity
and Azure Pipelines set no CI variable and were offered prompts nobody
was there to answer, and CI=false — an explicit denial — cost a
developer their prompt. Both are fixed.

Both hosts in this repo stop answering. The v8 bin no longer imports
ci-info, and ci-info moves from @prisma/cli's dependencies to the
engine's. The test harness keeps its isCI seed and run({ isCI })
override, now wired to isCIOverride; a test that says nothing about CI
gets detection over its own env.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@wmadden-electric, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 57 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9125e751-7e95-4a7e-a5fe-5de32cb81805

📥 Commits

Reviewing files that changed from the base of the PR and between 5724456 and ca918a8.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (26)
  • packages/cli-engine/package.json
  • packages/cli-engine/src/ci.ts
  • packages/cli-engine/src/context.ts
  • packages/cli-engine/src/execution/command-context.ts
  • packages/cli-engine/src/execution/shared-flags.ts
  • packages/cli-engine/src/runtime.ts
  • packages/cli-engine/src/telemetry/gating.ts
  • packages/cli-engine/src/telemetry/report.ts
  • packages/cli-engine/src/testing.ts
  • packages/cli-engine/tests/ci.test.ts
  • packages/cli-engine/tests/clack-isolation.test.ts
  • packages/cli-engine/tests/clack-prompts.test.ts
  • packages/cli-engine/tests/config.test.ts
  • packages/cli-engine/tests/engine.type-test.ts
  • packages/cli-engine/tests/environment-credential-manager.test.ts
  • packages/cli-engine/tests/execution.test.ts
  • packages/cli-engine/tests/lifetimes.test.ts
  • packages/cli-engine/tests/management-api.test.ts
  • packages/cli-engine/tests/prompts.test.ts
  • packages/cli-engine/tests/spawn.test.ts
  • packages/cli-engine/tests/telemetry-report.test.ts
  • packages/cli-engine/tests/telemetry-run.test.ts
  • packages/cli/package.json
  • packages/cli/src/v8/runtime.ts
  • packages/cli/tests/v8-telemetry-reporting.test.ts
  • packages/cli/tsdown.config.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Aug 12, 2026

Copy link
Copy Markdown

Open in StackBlitz

npx https://pkg.pr.new/@prisma/cli@155
npx https://pkg.pr.new/@prisma/cli-engine@155

commit: ca918a8

@wmadden-electric
wmadden-electric merged commit b7af84c into main Aug 12, 2026
12 checks passed
@wmadden-electric
wmadden-electric deleted the engine-owns-ci branch August 12, 2026 09:26
wmadden-electric added a commit that referenced this pull request Aug 12, 2026
Acceptance verified against source and the merged PRs; the spec gains
a Close-out section recording the two items that shipped amended (the
bin's Node floor is 22.18, not 24, via composer #224; ledger Q2 was
ruled dropped rather than closed by S3's mechanism). plan.md marks S3
closed and points at S8 as next. deferred.md gains the isCI item from
the handover brief: composer drops its ci-info answer at its next
engine-pin bump, because prisma-cli #155 made the engine detect CI
itself.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants