stablyai / stablyai/orca

[Bug]: Background /usage PTY spawns full Claude sessions, auto-accepts trust, and can execute tools/subagents

Open
#13,086 3 comments 0 reactions 1 assignee Claimed by @nwparker View on GitHub
os:macos
Dominant language
TypeScript
Stars
71.3k
Forks
4.7k
Avg merge
17h 8m
Merged PRs (30d)
475

Description

### Operating system

macOS

### Orca version

Reproduced on v1.4.174 and v1.4.176 (latest stable at the time).

Claude Code: 2.1.224.

### Details

#### Short summary

Orca's background Claude usage/rate-limit machinery starts a real interactive `claude` PTY without explicit user initiation. The PTY inherits the user's full Claude configuration, hooks, plugins, MCP servers, and authentication. Orca then sends `/usage`, automatically accepts trust prompts, and repeatedly presses Enter without verifying that Claude is actually at a safe command-palette state.

On my machine this created 82 Claude session UUIDs/artifacts within one 30-minute window. At least one earlier hidden probe escaped the intended local `/usage` flow and performed real model/tool activity (`Read` + `Agent`), including launching a Sonnet subagent. This means an informational background usage probe can consume Claude quota and potentially execute tools or mutate files through user-installed hooks/plugins.

This appears more severe than #12181: that report covers background refresh creating `~/.claude`; this report confirms real interactive sessions and model/tool execution.

#### What happened

Orca's source explicitly starts a full interactive Claude process:

- [`claude-pty.ts` describes an interactive `/usage` fallback](https://github.com/stablyai/orca/blob/8c3e9535c75b379ca9ac68b5d35ddfca196ea3df/src/main/rate-limits/claude-pty.ts#L20-L22)
- [prompt detection is intentionally skipped in favor of a fixed 2-second delay](https://github.com/stablyai/orca/blob/8c3e9535c75b379ca9ac68b5d35ddfca196ea3df/src/main/rate-limits/claude-pty.ts#L166-L177)
- [`pty.spawn()` launches `claude` with no arguments in `rate-limit-pty-cwd`](https://github.com/stablyai/orca/blob/8c3e9535c75b379ca9ac68b5d35ddfca196ea3df/src/main/rate-limits/claude-pty.ts#L238-L296)
- [the PTY presses Enter every 800 ms](https://github.com/stablyai/orca/blob/8c3e9535c75b379ca9ac68b5d35ddfca196ea3df/src/main/rate-limits/claude-pty.ts#L381-L391)
- [it sends `/usage`, starts the Enter loop, and automatically answers trust prompts with `y`](https://github.com/stablyai/orca/blob/8c3e9535c75b379ca9ac68b5d35ddfca196ea3df/src/main/rate-limits/claude-pty.ts#L431-L464)

The resulting Claude session hooks contained transcript paths such as:

```sh
export CODEX_COMPANION_SESSION_ID='700f543a-8bc8-42f7-8ca7-085bd20def2d'
export CODEX_COMPANION_TRANSCRIPT_PATH='$HOME/.claude/projects/-Users-user-Library-Application-Support-orca-rate-limit-pty-cwd/700f543a-8bc8-42f7-8ca7-085bd20def2d.jsonl'
```

In the 30 minutes ending at 04:02:32 local time, I counted **82 distinct session IDs** whose transcript path resolved to Orca's synthetic working directory:

```text
$HOME/Library/Application Support/orca/rate-limit-pty-cwd
```

The final burst created new session IDs approximately every 5–7 seconds:

```text
04:01:03 86238cb1-b103-4774-8962-f2a35ad41ed5
04:01:10 ff86644c-9b09-4c62-9e1d-9e8992577594
04:01:16 dc6e0330-f2fd-4927-919e-cbce0b8d2d8c
04:01:21 0d135a56-3fc7-4038-b4d5-825ce965b417
04:01:27 0b695541-446e-40fa-919d-66b2195e6d80
04:01:32 692dbd50-2a68-447b-bbd5-b59b8712c7fa
04:01:37 82c0f436-d7fd-42eb-a718-206ab56765dc
04:01:43 bdbbb404-2d9b-4a3a-b44f-d7f11aa7ed63
```

Because a single PTY repeatedly presses Enter, the count of Claude session UUIDs is not necessarily identical to the count of OS processes. However, I also captured a real live Claude process and its ancestry:

```text
Orca main
-> Orca daemon-entry.js
-> orca-tcc-login
-> login zsh
-> claude --dangerously-skip-permissions --verbose
-> configured MCP/plugin child processes
```

Concrete PIDs from the capture:

```text
6542 Orca
6917 Orca daemon-entry.js
40547 orca-tcc-login
40579 login zsh
41427 claude --dangerously-skip-permissions --verbose
```

Immediately before that PTY appeared, `daemon.log` recorded:

```json
{"ts":"2026-08-07T20:00:29.456Z","event":"client-hello-accepted","clientId":"resolver-health-check"}
{"ts":"2026-08-07T20:00:29.475Z","event":"macos-tcc-pty-spawn","strategy":"wrapped"}
{"ts":"2026-08-07T20:00:29.476Z","pid":40547,"event":"session-created","sessionId":"folder:@@d64caabf"}
{"ts":"2026-08-07T20:00:57.468Z","event":"session-exited","sessionId":"folder:@@d64caabf","code":0}
```

The Claude session created at the same time still used Orca's hidden `rate-limit-pty-cwd`, not the visible project directory.

#### Confirmed model/tool activity from an earlier hidden probe

A separate hidden session on v1.4.174 had this Claude session-stat entry:

```json
"9cd4d201-42b8-422e-aee1-1822b1351b9d": {
"tool_counts": {
"Read": 1,
"Agent": 1
},
"last_tool": "Agent",
"total_calls": 2,
"started_at": 1786124400,
"updated_at": 1786124450
}
```

Its transcript/project namespace was also Orca's hidden cwd:

```text
$HOME/.claude/projects/-Users-user-Library-Application-Support-orca-rate-limit-pty-cwd/
```

The `Agent` tool created a real Sonnet subagent:

```json
{
"agentType": "codex:codex-rescue",
"description": "Repair backup script: fix checksum path, add progress output, populate empty folders"
}
```

OMC recorded the mission starting at `2026-08-07T17:40:16.986Z` and completing at `17:40:49.962Z`. The parent session ended at `17:41:03.946Z`.

I could not recover the deleted parent transcript, so I cannot prove what upstream text caused that unrelated task description to be generated. I can prove that Orca created the parent Claude session in its hidden rate-limit cwd and that the session made model/tool calls.

#### Behavior while Claude was already at its limit

At the time of the 82-session burst, Claude usage showed 100% used for the five-hour window. The repeated sessions produced limit notifications, but there was no new successful tool activity in `~/.claude/.session-stats.json` during that specific 30-minute burst.

That suggests those attempts were rejected at the quota boundary. However, the same behavior after the quota reset could make successful calls again. The earlier `9cd4...` session proves the PTY path can reach real inference and tools.

#### Steps to reproduce

1. On macOS, install Orca v1.4.176 and authenticate Claude Code.
2. Have ordinary Claude global hooks/plugins enabled. The problem does not require an Orca automation.
3. Start Orca and focus/show/restore its main window so usage refresh runs.
4. Let Claude usage fetching fall back to the PTY path.
5. Inspect `~/.claude/session-env/*/sessionstart-hook-*.sh`.
6. Observe new session IDs whose transcript paths contain `Library-Application-Support-orca-rate-limit-pty-cwd`.
7. Put the Claude account at its five-hour limit and keep Orca focused.
8. Observe repeated limit notifications and additional hidden session IDs.
9. Monitor the process tree to see real `claude` processes spawned beneath Orca/its daemon.

#### Expected behavior

Background usage tracking must not create an inference-capable Claude session or load the user's hooks, plugins, MCP servers, project instructions, or tool permissions.

A usage probe must never:

- automatically accept a trust prompt;
- blindly press Enter without identifying the exact TUI state;
- allow text to become a normal model prompt;
- execute tools/subagents;
- retry in a way that creates many Claude sessions;
- consume the user's Claude subscription quota.

#### Suggested fixes

1. Remove the interactive Claude PTY fallback for automated/background usage refresh. Prefer the OAuth usage endpoint and the existing live statusline usage feed.
2. If the PTY fallback must remain, make it explicit/manual opt-in and provide a setting to disable it.
3. Never auto-accept trust prompts in a background usage probe.
4. Do not send Enter repeatedly. Require an exact, versioned TUI state before sending any input and fail closed otherwise.
5. Launch probes with hooks/plugins/MCP initialization disabled and without dangerous permission flags.
6. Add single-flight protection and persistent exponential backoff keyed by provider/account.
7. Gate the path when Claude is disabled or when no managed/selected Claude account exists.
8. Add a regression test proving that background usage refresh cannot create a Claude transcript/session or invoke model/tool hooks.

#### Related issues

- #12181 — background usage refresh creates `~/.claude` even when Claude is disabled.
- #9616 — usage 429 handling and focus/restore retry behavior.

No credentials, auth files, or raw environment values are included in this report.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.