Skip to content

test(claude): run the real Claude CLI tests only when opted in - #23702

Merged
brennanb2025 merged 7 commits into
mainfrom
brennanb2025/real-agent-tests-opt-in
Sep 30, 2026
Merged

brennanb2025 merged 7 commits into
mainfrom
brennanb2025/real-agent-tests-opt-in

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 2 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​88 $\color{#cf222e}{\Huge{\mathbf{−}}}$​1 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​87
Prod 3 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​65 $\color{#cf222e}{\Huge{\mathbf{−}}}$​17 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​48

ELI5

One test file talks to the real Claude command-line tool on your computer, using your own Claude account. Until now it ran by itself whenever it found Claude installed and signed in. Now it only runs when you ask for it by setting ORCA_REAL_CLAUDE_CLI_TEST=1.

What Changed

Before: running a broad local test command (a folder, the whole suite, or a file list that accidentally came out empty) also ran src/main/claude/claude-structured-real-cli.test.ts on any machine where claude --version worked and claude auth status reported a login. That spent real turns on the developer's Claude account and left real transcripts in their Claude home folder. It has happened four times.

After: that suite is skipped unless ORCA_REAL_CLAUDE_CLI_TEST=1 is set. The skipped suite's title says so: Claude structured real CLI handshake (skipped: set ORCA_REAL_CLAUDE_CLI_TEST=1 to run against the real claude CLI). With the variable set it behaves as before, except that it reads the account report strictly (a field of the wrong type is dropped instead of cast), including skipping when claude is missing and skipping the signed-in cases when there is no login.

Mechanism: a small gate, resolveRealClaudeCliGate in src/main/claude/claude-real-cli-test-gate.ts, checks the variable first and returns before it runs any claude probe. Only when opted in does it run claude --version and then claude auth status --json, and it reads the account report with a checked parse instead of a cast. The test file passes in the process-spawning probe, so the gate itself spawns nothing and is unit-tested with a fake.

Since this PR opened, main added a second real-CLI suite (claude-structured-real-cli-fold.test.ts) and moved the probe both suites use into claude-real-cli-availability-test-support.ts, which ran claude on import with no opt-in. That shared module now takes its answer from the same gate, so neither suite runs, or even probes, the real CLI unless ORCA_REAL_CLAUDE_CLI_TEST=1 is set, and AGENTS.md names both suites. Checked with the process runner mocked: with the variable unset nothing is probed and the suites report the CLI unavailable; with it set, the two probes run as before.

The variable follows the naming the repo already uses for suites that touch real resources on the developer's machine: ORCA_REAL_PROCESS_SKILL_TEST, ORCA_REAL_WSL_SKILL_TEST, ORCA_REAL_WINDOWS_SKILL_TEST, each checked as === '1'. No Claude variable existed, so this adds one in that family.

What the suite covers

It is the only test that talks to a real Claude CLI: the structured-session handshake (a session id minted before the first message), the settings and model each turn reports, a follow-up withdrawn when the turn ahead of it is stopped (as the real CLI confirms it), sign-in guidance on an unauthenticated start, and where the transcript file lands.

No CI job ran it before this change (no workflow installs Claude, so every case skipped), and none runs it after. CI coverage is unchanged; the only thing that stops is accidental local runs on a developer's account. AGENTS.md now says when to run it on purpose.

Will real problems still get caught?

Turning off the default run does not remove a safety net, because the default run was never a reliable one:

  • CI never ran these tests. No CI job installs Claude, so every case was skipped on every pull request, before this change and after it. The only runs that happened were on developer machines, and only when a broad test command happened to include this file.
  • Those accidental runs did not catch drift either. Run on purpose for this PR against Claude 2.1.280, one case fails: a project command in .claude/commands no longer appears in the command list. It had failed the same way in earlier accidental runs today, and nobody acted on it, because a failure buried in a broad run looks like noise. That failure needs its own fix, which is tracked separately.
  • The accidental runs had a real cost. Each one spent real turns on the developer's Claude account and wrote transcripts into their real Claude folder. That has happened five times.

How agents will know to run it:

  • AGENTS.md, which every agent working in this repo reads at the start of a session, now says: when you change Claude structured-session code (src/main/claude/claude-structured-*), run ORCA_REAL_CLAUDE_CLI_TEST=1 pnpm test src/main/claude/claude-structured-real-cli.test.ts src/main/claude/claude-structured-real-cli-fold.test.ts.
  • The skipped suite names itself. In any test run, the suite title reads "skipped: set ORCA_REAL_CLAUDE_CLI_TEST=1 to run against the real claude CLI", so anyone looking at the output sees what was skipped and how to run it.

What is still not covered, stated plainly: an agent that changes Claude code without reading that line, or ignores it, will not run these tests, and CI will not catch a regression they would have caught. That was equally true before this change, because CI never ran them. The fix for that gap is a follow-up: record real Claude sessions once and replay them in a normal unit test, so the Orca side of these cases is checked on every pull request without a Claude account, leaving only changes in Claude itself to the opt-in run.

Where these real-Claude tests came from

These suites were added, and extended, by these PRs:

Why

Having Claude installed and signed in is not consent to spend that account's turns or write into its home folder. The old gate inferred consent from credentials, so every broad run on a developer machine was an opt-in by accident.

An explicit variable is the rule the other real-binary tests already follow: the Codex binary contract needs ORCA_CODEX_CONTRACT_BINARY, the real-Codex orchestration test needs ORCA_REPRO_CODEX_BINARY, and the real-agent end-to-end specs need ORCA_E2E_REAL_CODEX, ORCA_E2E_REAL_CODEX_IME, ORCA_E2E_REAL_OPENCODE or ORCA_E2E_REAL_REMOTE_CODEX. This was the one real-agent test that used the developer's signed-in account without being asked.

Alternatives considered: excluding the file from the default vitest include list would stop it running even when a developer asks for it by path, and would hide it from the suite entirely. Gating only the signed-in cases would still spawn the real CLI on every broad run. The gate checks the variable before any probe, so a broad run no longer touches claude at all.

CI jobs and the variable: no CI job ran this suite's tests before, and none does now, so no workflow needs the variable.

  • Unit tests / test (called from PR Checks on ubuntu-24.04-arm, and from node-next-compat on Node 24 and 26): collects the file in its shards, but no workflow installs claude, so every case skipped before on the missing binary (the recorded shard timing for the whole file is 257 ms, far too short for a real session) and skips now on the missing variable. Variable not set; not needed.
  • PR Checks / Codex index-heal contract: installs the Codex CLI only and runs one named Codex file. Not affected.
  • Every other job that runs vitest passes an explicit file list that does not include this file (Git compatibility, shell contracts, orcad browser provider, cross-version wire compatibility, package, package (windows), release-ref-validation, the skill-sharing release lanes, and the Bun profile lanes). Not affected.

Linked Issue

N/A: internal test-safety fix, no issue.

Visual Proof

N/A: test-only change, no UI or runtime behavior change.

Testing

  • src/main/claude/claude-real-cli-test-gate.test.ts covers the gate both ways with a fake claude that is installed and signed in. Unset, empty, 0 and true all skip with the message naming the variable, and the fake is never called. 1 runs, returns the account report, and probes --version then auth status --json in that order. Opted in with no binary still skips. A failed or unreadable auth report still runs, with no report, so only the signed-out case runs. Account fields of the wrong type are dropped.

  • With the variable check removed from the gate, the four "skips without probing" cases fail, and the other five still pass.

  • Ran the gate test plus claude-structured-real-cli.test.ts with the variable unset: 9 passed, and all 6 real-CLI cases were reported as skipped under the title naming the variable. Also ran the real-CLI suite once with ORCA_REAL_CLAUDE_CLI_TEST=1, against the real signed-in Claude CLI (2.1.280) with its login in place, on this branch:

    • passed: reports the current effort through get_settings and never on the init frame; writes its transcript where the mobile session-file resolver looks for it; reports the model it adopted on the init frame of each turn; withdraws a follow-up queued behind a turn that is stopped, behind a SessionStart hook; turns a real silent unauthenticated startup into sign-in guidance.
    • failed: "proves a pre-minted session before the first user message", at its check that a project command seeded in .claude/commands appears in the command list. Claude 2.1.280 listed 72 commands without it. This case failed the same way in two earlier local runs today, before this change, so it is drift between the test and the current Claude release, not something this gate causes. It needs its own fix.
  • oxlint on the changed files, and pnpm run check:code-quality:changed -- origin/main: clean.

  • Platforms: macOS locally. The change is a plain environment check and has no platform-specific paths.

  • I manually tested these changes locally

  • Automated tests added/updated, or explained why not below

AI Disclosure

Review

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

Security: a developer's real account is no longer used by default. Cross-platform: the Windows spawn path used by the probes is unchanged. SSH, mobile, backward compatibility and performance: not affected; this only changes which tests run.

To run the real suites on purpose: ORCA_REAL_CLAUDE_CLI_TEST=1 pnpm exec vitest run --config config/vitest.config.ts src/main/claude/claude-structured-real-cli.test.ts src/main/claude/claude-structured-real-cli-fold.test.ts

Author X: @BrennanKB5

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

An installed, signed-in claude is not consent to spend turns on that account.
The gate skips before probing the binary unless ORCA_REAL_CLAUDE_CLI_TEST=1.
The suite used to run whenever `claude --version` succeeded and `claude auth status`
reported a login, so any broad local vitest run spent real turns on the developer's
account and wrote transcripts into their Claude home. It now also requires
ORCA_REAL_CLAUDE_CLI_TEST=1, and the skipped suite's title names the variable.
@brennanb2025
brennanb2025 marked this pull request as ready for review September 28, 2026 21:54
@greptile-apps

greptile-apps Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[Low risk] Adds opt-in gate for real Claude CLI tests.

The PR appears safe to merge; the real CLI suites require explicit opt-in before probing Claude.

Summary

The PR makes the real Claude CLI suites opt-in. Without ORCA_REAL_CLAUDE_CLI_TEST=1, the shared gate skips the suites before probing Claude; with it, the gate checks CLI availability and parses the account report. Unit tests cover both paths, and AGENTS.md documents how to run the suites intentionally.

Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Test suite loads] --> B{Opt-in equals 1?}
  B -->|No| C[Skip without CLI probe]
  B -->|Yes| D[Probe Claude version]
  D --> E{CLI available?}
  E -->|No| F[Skip suite]
  E -->|Yes| G[Read auth status and run eligible cases]
Loading

Reviews (5) · Last reviewed commit: "Merge origin/main (a57863ba167, #23940) ..."

@coderabbitai

coderabbitai Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: a3ae5ec7-3a87-47d3-a38f-e0bd9d755d82

📥 Commits

Reviewing files that changed from the base of the PR and between 31c413f and 1d2ce27.

📒 Files selected for processing (3)
  • AGENTS.md
  • src/main/claude/claude-real-cli-availability-test-support.ts
  • src/main/claude/claude-structured-real-cli.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/claude/claude-structured-real-cli.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

Adds an opt-in gate for tests that use the real Claude CLI. The gate skips without probing unless ORCA_REAL_CLAUDE_CLI_TEST is exactly 1. When enabled, it checks the CLI version and parses supported authentication fields from a successful auth status probe. The structured CLI test suite now uses this gate for its skip reason and authentication status.

Priority: ⬇️ Low

Merge Risk: ⚪ Minimal · up to 1d2ce

The two documented structured CLI suites do not use Claude unless explicitly opted in, and the separate supervised-stop suite has its own opt-in. No actionable merge-blocking risk is established.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 1d2ce

The real-account tests now require an explicit opt-in before checking or using the Claude CLI. No new production entrypoint or broader account access was identified. The assessment is limited to the inspected test paths and does not establish runtime behavior for every test configuration.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The sensitive outcome is use of a developer’s authenticated CLI account and local transcripts during opted-in tests, rather than a newly reachable production service or cross-tenant path.

Trust Boundaries and Controls

  • observed — An installed or signed-in CLI alone does not authorize these tests: the exact opt-in is checked before any version or account probe, and consumers derive availability from the gate result.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 4 files. (1 skipped: 1… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the main change: making the real Claude CLI tests run only when explicitly opted in.
Description check ✅ Passed The description is detailed and covers the required sections, including the user-facing change, rationale, implementation, testing, platform considerations, and checklist. It explains why the linked i…
Full details: Docstring Coverage

Explanation

Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 4 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR

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.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

  • Opt-in gate for the real Claude CLI suite — resolveRealClaudeCliGate checks ORCA_REAL_CLAUDE_CLI_TEST=1 before any subprocess, so a broad local run no longer probes or drives the developer's signed-in claude account.
  • Strict account report parsing — parseAuthStatus drops loggedIn/projectsDirectory fields of the wrong type instead of casting, so an unrecognized shape degrades to a skip rather than a runtime throw.
  • Real suite rebased on the gate — the inline spawnSync probes are replaced with the gate, and describe.skipIf(gate.skipReason !== null) plus a title that names the variable make an unset opt-in self-explanatory in test output.
  • Unit coverage for the gate — a fake claude pins the skip-without-probing cases, probe order, missing-binary skip, failed/unparseable auth, and wrong-typed field dropping.

I ran the two files through --config config/vitest.config.ts: 9 gate tests pass and all 6 real-CLI cases report skipped with the variable unset, confirming the gate fires before any probe. I also confirmed no other suite under src/main/claude/ spawns the real binary (the connection and exit-proof suites use scripted/fake CLIs), so the scope claim holds.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@brennanb2025
brennanb2025 marked this pull request as draft September 28, 2026 23:11
@brennanb2025
brennanb2025 marked this pull request as ready for review September 28, 2026 23:19

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

  • Opt-in gate extracted — new src/main/claude/claude-real-cli-test-gate.ts returns a skip reason before any probe unless ORCA_REAL_CLAUDE_CLI_TEST === '1', then checks claude --version and claude auth status --json with a strict field parse.
  • Real suite gated — claude-structured-real-cli.test.ts now resolves the gate at module load and uses describe.skipIf(gate.skipReason !== null) with a reason-bearing title; signed-in cases still require loggedIn === true.
  • Gate unit tests — 9 cases cover unset/''/'0'/'true' skipping without probing, the opt-in probe order and payloads, a missing binary, failed/unparseable auth, and wrong-typed account fields.
  • AGENTS.md — records when to run the opt-in suite under Verifying Changes.

Verified locally: the gate suite passes (9/9), the change is test-only in practice (the gate has no production importer), and no central env-var doc lists the sibling ORCA_REAL_* variables, so no doc update is implied.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

brennanb2025 added a commit that referenced this pull request Sep 29, 2026
…3614) into #23059

Conflicts, resolved to keep main's mechanism and #23059's turn facts:
- Codex turn boundaries and translator: main's #23682 wins; the error-driven
  `fail` end and the oversized-notification settler are gone, and only
  `turn/completed` ends a turn, a command's turn included.
- Codex adapter: the compaction tracker stays deleted; #23618's settlement of
  sends in an ended turn is kept, and `codexDispatchRejection` is imported from
  its new home.
- Journal reducer: main's per-item creating fence (#23456) moves into the item
  and submission folds this branch split out of the reducer.
- Stale-turn verdict: main's proof-of-death revision; every turn revision states
  the thread scope.
- Failure words: main's retry-control rules (#23608) with this branch's any-command
  retry step; `commandRefused` drops its retry step beside a Retry, as hostFault does.
- Delivery: both the commit wake and main's death-evidence resettle and dispose.
- Fold row and transcript slots: both the turn-outcome flag and main's `agentId`.
- pr.yml and pnpm-lock.yaml taken from main.

Beyond the conflicts, the merged files needed:
- use-structured-agent-session: both sides added the same `submissions` key; one kept.
- structured-agent-session-projection: the root-item import main still used is unused here.
- The desktop message list and the mobile chat view each grew past the line limit
  from both sides' additions: the turn-membership memo moves into its own hook, and
  the mobile list footer (live status, then waiting rows) into its own helper.
main's #23621 moved the real-Claude probe into a shared support module that both
real-CLI suites import, probing on import with no opt-in. That module now takes
its availability from this PR's opt-in gate, so neither suite probes or runs the
CLI unless ORCA_REAL_CLAUDE_CLI_TEST=1, and AGENTS.md names both suites.
@greptile-apps

greptile-apps Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Comments Outside Diff

These findings could not be posted inline.

  • P2 Live interruption test can race src/main/claude/claude-structured-real-cli.test.ts:332 ▶

    The first send is marked admitted when it reaches the input pump, not when Claude has reached a guaranteed pre-reply window. If Claude starts replying before the interrupt takes effect, this test fails its no-reply assertion even when cancellation works correctly. That can make an intentional real-CLI test run fail spuriously.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

Since the prior pullfrog review (79bc8f6), the branch merged main twice and reconciled with main's newer shared real-CLI support module:

  • Shared probe now routes through the opt-in gate — claude-real-cli-availability-test-support.ts derives realClaudeAvailable and realClaudeAuthStatus from resolveRealClaudeCliGate, so the newer fold suite, which consumes that module, also skips without ever probing when ORCA_REAL_CLAUDE_CLI_TEST is unset.
  • Real-suite title hoisted to a module const — suiteTitle keeps the skip reason on the main suite while avoiding a whole-file re-indent of the merged describe call.
  • Opt-in gate unchanged — claude-real-cli-test-gate.ts and its test are byte-identical to the prior review (git diff 79bc8f6...HEAD on both is empty).

I ran the gate suite plus both real-CLI suites with the variable unset: 9 gate tests pass, the structured suite (7) and fold suite (1) report skipped, 1.2s total — the gate short-circuits before any claude spawn even through the shared support module. resolveClaudeCommand() still runs at import but only does filesystem path resolution; the already-opt-in claude-supervised-stop-real-cli suite is unaffected.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@brennanb2025
brennanb2025 merged commit f094587 into main Sep 30, 2026
33 checks passed
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.

1 participant