Skip to content

fix(linux): repair disabled session bus in daemon and PTY environments - #21120

Closed
oliver-mee wants to merge 4 commits into
stablyai:mainfrom
oliver-mee:fix/linux-session-bus-env
Closed

oliver-mee wants to merge 4 commits into
stablyai:mainfrom
oliver-mee:fix/linux-session-bus-env

Conversation

@oliver-mee

@oliver-mee oliver-mee commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

ELI5

When Orca runs headless on Linux, every terminal it opens gets a broken setting that tells tools there is no user session bus. So systemctl --user fails in those terminals, although the bus is fine. This PR puts the real bus address back before the terminal starts.

What Changed

Before: headless orca serve starts with no session bus in its env, so Chromium sets DBUS_SESSION_BUS_ADDRESS=disabled: in its own env. Service hardening can set the same marker. Orca passes the marker into the forked terminal daemon and into every PTY shell and agent. In those shells, systemctl --user and systemd-run --user fail with "Failed to connect to bus: Connection refused", while the bus at /run/user/<uid>/bus is healthy.

After: those shells get DBUS_SESSION_BUS_ADDRESS=unix:path=<runtime dir>/bus, and user-bus tools work.

Mechanism:

  • New repairDisabledSessionBusEnv in src/main/pty/dbus-session-bus-env.ts. It is Linux only, and it changes the variable only when the value is exactly disabled:. It finds the bus with resolveUserRuntimeDir from daemon-cgroup-scope.ts, the same resolver the durable daemon scope uses: an isSocket() check, the per-UID runtime dir first, XDG_RUNTIME_DIR as a fallback. When no bus is reachable, the env does not change. That resolver and its constant are now exported, with no change in behaviour.
  • Called on three spawn paths: the daemon launch env in daemon-launched-child-spawn.ts (this feeds both the direct fork and the systemd-run --user --scope arm), the daemon PTY env in pty-subprocess/spawn-environment.ts, and the local PTY env in providers/local-pty-spawn-environment.ts.

Why

A real address, not a deleted variable: shells can carry an XDG_RUNTIME_DIR that points at a private hardening dir with no bus in it (the case the resolveUserRuntimeDir comment describes), so a shell tool cannot rediscover the bus on its own. The strict disabled: match leaves every other value alone, including a real address and an absent variable.

This also affects #19430. Its durable-scope launch runs systemd-run --user --scope with the daemon env. With the marker in that env, systemd-run --user fails (reproduced on systemd 255: DBUS_SESSION_BUS_ADDRESS=disabled: systemd-run --user --scope --collect true exits 1 with "Connection refused", and the same command without the variable works). So under headless serve the scoped launch falls back to the unscoped path. The legacy migration and the scope death watch already clear the marker, but buildDurableDaemonScopeCommand does not. The repair on the daemon launch env covers that arm too.

Alternatives considered: delete the marker (as the migration and death watch do), which fails in shells for the reason above. Or a separate path check, which was the first version of this PR and duplicated the existing resolver with a weaker existsSync check.

Linked Issue

Fixes #21119

Visual Proof

N/A. No UI changes. The change is to the environment of spawned processes on Linux.

Testing

  • I manually tested these changes locally
  • Automated tests added/updated, or explained why not below

Local run, Linux arm64:

  • dbus-session-bus-env.test.ts: the helper against real Unix sockets through injected platform and runtime-dir seams (per-UID bus, per-UID over a hardened XDG_RUNTIME_DIR, XDG_RUNTIME_DIR fallback, no bus, a plain file named bus, real and absent addresses, non-Linux). Unix-socket cases are skipped on Windows, where Node requires named-pipe endpoints.
  • dbus-session-bus-env-wiring.test.ts and daemon-launched-child-spawn.test.ts: each of the three spawn paths hands on the repaired address. Removing any one of the three calls makes these tests fail. I checked this for each call.
  • Related Vitest suites for src/main/pty, daemon PTYs, local PTYs, daemon launch and scope: 45 files passed, 547 tests passed and 22 were skipped on Linux arm64.
  • pnpm run typecheck:node passed. pnpm run check:code-quality:changed passed on all 8 changed files. The commit hook's oxlint, React Doctor and formatting checks passed.
  • Manual, before this change: on a headless Linux host, a shell spawned by orca serve shows disabled:, and systemctl --user fails. Setting the repaired value by hand makes systemctl --user is-system-running print running.

Not tested: a full end-to-end serve run with this build. The marker failure was manually reproduced before the fix, but I did not run a post-fix serve session. Repo CI has not run on this PR yet because it waits for maintainer approval.

AI Disclosure

Earlier revisions: Muse Spark via OpenCode. Rebase onto #19430, the move to the shared resolver, the local PTY path and the new tests: Claude Opus 5.5 (claude-opus-5-5) with agentic tool use (shell, file edits, test runs). The Windows test guard and docstring follow-up used OpenAI GPT-6 Luna (gpt-6-luna) via OpenCode. A human directed and reviewed the work.

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

Ensure no issues in: Security, Cross-platoform support (Linux, Windows, Mac), Remote SSH, Mobile, general backwards compatibility, performance

The repair is Linux only and a no-op off Linux. It runs only when the value is exactly disabled:, so desktop sessions with a real bus do not change. It makes one stat() call per spawn only in that case. No SSH, mobile or relay code changes.

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)

The last box is not ticked: related tests, the node typecheck and changed-code quality gate passed. Full pnpm tc was terminated with exit code 137 in the container. Full pnpm lint, pnpm test and pnpm build were not run.


🤖 Generated with Muse Spark, Claude Opus 5.5, and OpenAI GPT-6 Luna
🧑‍💻 Ideated, directed and reviewed by a human, @oliver-mee

@coderabbitai

coderabbitai Bot commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

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

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

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: 59faacb8-55a8-4fde-a030-3d187522bd8c

📥 Commits

Reviewing files that changed from the base of the PR and between a8d0c428545c6ced8857d3328f559855674721cd and d7db8d3.

📒 Files selected for processing (3)
  • src/main/daemon/pty-subprocess/spawn-environment.ts
  • src/main/providers/local-pty-spawn-environment.ts
  • src/main/pty/dbus-session-bus-env.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

A new helper repairs DBUS_SESSION_BUS_ADDRESS when it equals disabled: on Linux and the runtime-directory resolver finds a reachable bus. Daemon child, daemon PTY, and local PTY environment setup call the helper. Tests cover runtime-directory selection, socket checks, unchanged environment values, platform behavior, and environment wiring.

Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to d7db8

The reviewed changes are mergeable after normal checks; no concrete session-bus regression remains identified.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to d7db8

Linux daemon processes and terminals can now receive a working user-bus address where they previously received a disabled marker. That repairs the reported failure, but it may conflict with deployments that deliberately disable bus use for certain processes.

Retained concerns

  • Medium · security · inferred: Repair also overrides an explicitly supplied disabled session-bus marker, potentially defeating a deployment's intended per-process bus opt-out.
Security review details

Security Blast Radius

  • inferred — On affected Linux launches, daemon descendants and local PTY shells or agents can inherit an automatically selected user-bus address. Effective bus access still depends on socket and bus authorization.

Trust Boundaries and Controls

  • observed — Repair requires Linux, the exact disabled: marker, and a socket at a selected bus path. A caller-supplied real address was already able to override an inherited marker in the local PTY environment merge.

Hardening Proposals

  • proposed — If any deployment uses an explicit disabled: value as a bus-use policy, distinguish that choice from an inherited broken marker before repairing PTY environments.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 77.78% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 9 functions across 9 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: repairing the disabled Linux session bus in daemon and PTY environments.
Description check ✅ Passed The description is complete and follows the required template. It explains the user impact, implementation, rationale, linked issue, testing, platform considerations, AI disclosure, and incomplete ful…
Linked Issues check ✅ Passed For #21119, the PR repairs the exact DBUS_SESSION_BUS_ADDRESS=disabled: value in Linux daemon and PTY environments. The repair uses a reachable user-bus socket and leaves other values, platforms, an…
Out of Scope Changes check ✅ Passed The changes stay within #21119. The exported runtime-directory helpers support the session-bus repair. The platform guards, environment documentation, mocks, and wiring tests support the same behavior…
  • Fix all pre-merge checks with AI

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 critical issues — one minor suggestion inline.

Reviewed changes

  • Session-bus repair helper — new Linux-only repairDisabledSessionBusEnv rewrites an exact DBUS_SESSION_BUS_ADDRESS=disabled: to unix:path=/run/user/<uid>/bus, but only when that socket actually exists.
  • Daemon fork env — the inline fork env became daemonForkEnv(), which repairs the environment inherited by the forked daemon so the marker never originates there.
  • Daemon PTY env — createDaemonPtyEnvironment repairs the merged env too, which also catches a disabled: value arriving through opts.env.
  • Tests — six cases: repair, missing socket, real address, absent var, non-Linux, and a live no-throw smoke test.

I verified the load-bearing external claims: Chromium really does setenv("DBUS_SESSION_BUS_ADDRESS", "disabled:", 0) when the var is unset at launch and it does surface in the Electron main process, and unix:path=/run/user/<uid>/bus matches the address systemd's own dbus.socket publishes via %t ($XDG_RUNTIME_DIR). Nice, tightly scoped fix.

ℹ️ Local PTY fallback still propagates the marker

The repair covers the daemon lane, but buildLocalPtySpawnEnvironment builds its env from the Electron process's process.env with no equivalent repair, so the marker still reaches shells whenever the daemon lane degrades to LocalPtyProvider (degraded-new-pty-fallback, or onDaemonError in main-process-pty-startup.ts). The reported headless-serve case is fixed while the daemon runs, but the PR's "every spawned PTY shell" claim does not hold on that fallback lane.

Technical details
# Local PTY spawn env is not repaired

## Affected sites
- `src/main/providers/local-pty-spawn-environment.ts:20` — spreads `stripInheritedBuildModeEnv(process.env)` with no `repairDisabledSessionBusEnv` call.
- `src/main/ipc/pty/host-env/assembly.ts:37` (`buildPtyHostEnv`) — the local lane's `baseEnv` includes `process.env`; the daemon lane's does not (see its own note at line 45).

## Required outcome
- The local PTY lane should produce the same session-bus address as the daemon lane, so a fallback spawn does not reintroduce the bug the PR fixes.

## Open questions for the human
- Is the local fallback lane intentionally out of scope (e.g. it is already non-persistent degraded mode), or should this be a follow-up? If in scope, calling `repairDisabledSessionBusEnv` in `buildLocalPtySpawnEnvironment` (or once in `buildPtyHostEnv` for the local path) is the smallest change.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Comment thread src/main/pty/dbus-session-bus-env.ts Outdated
@oliver-mee
oliver-mee force-pushed the fix/linux-session-bus-env branch from 1bf345c to c95d446 Compare September 24, 2026 11:23
@oliver-mee

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (now at 122b8c25d, "fix(terminal): let Linux IMEs keep the candidate key for a preedit they own (#22607)") to clear the conflict.

The branch was rebuilt on the new base rather than replayed, so the commit SHAs changed and the four commits are now two. #19430 moved the daemon launch out of daemon-launched-child.ts into daemon-launched-child-spawn.ts, so the repair now sits on daemonEnv there, in the new structure. That env feeds both the direct fork and the new systemd-run --user --scope arm. spawn-environment.ts was hand-applied on top of the #19512 changes. No logic changes to the repair itself.

One thing worth flagging from the rebase: with the marker left in place, systemd-run --user itself fails. On a systemd 255 host, DBUS_SESSION_BUS_ADDRESS=disabled: systemd-run --user --scope --collect true exits 1 with "Failed to connect to bus: Connection refused", while the same command without the variable works. So under headless serve the durable-scope launch from #19430 falls back to the unscoped path. The legacy migration and the scope death watch already clear this variable, but buildDurableDaemonScopeCommand does not. Because this PR repairs daemonEnv before it reaches that builder, it covers that arm too.

Validated locally: the related suites pass (dbus-session-bus-env, daemon-launched-child-spawn, daemon-launched-child-identity, daemon-cgroup-scope, 37 tests), the node typecheck is clean, and oxlint is clean on the changed files. Mergeable again.

@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 in this delta. The changes are a rebase onto current main plus one genuinely new wiring site. One prior minor suggestion (dbus-session-bus-env.ts:14) remains open for the author.

Reviewed changes

  • Rebased onto current main — base moved de4dab9 → 122b8c2, and the four prior commits were rewritten into two. The helper and the createDaemonPtyEnvironment call are byte-identical to the last review.
  • Daemon repair relocated into the refactored spawn path — the repair moved from the old daemon-launched-child.ts daemonForkEnv() into spawnDaemonChildProcess in daemon-launched-child-spawn.ts, computed once onto daemonEnv.
  • systemd-run --user --scope arm now repaired as well — because daemonEnv feeds both the direct forkProcess arm and buildDurableDaemonScopeCommand(...).env, the scoped launch no longer inherits the disabled: marker, so it can no longer fail to connect and fall back to the unscoped path for that reason.

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

@coderabbitai coderabbitai 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.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 26d2f9c1-ba85-40be-9f5a-8f41d17f714f

📥 Commits

Reviewing files that changed from the base of the PR and between c95d446768efba59825aa80f1094977f61ac11f9 and 069ff8d9902188a06e943372634a438702dea6f6.

📒 Files selected for processing (6)
  • src/main/daemon/daemon-cgroup-scope.ts
  • src/main/daemon/daemon-launched-child-spawn.test.ts
  • src/main/providers/local-pty-spawn-environment.ts
  • src/main/pty/dbus-session-bus-env-wiring.test.ts
  • src/main/pty/dbus-session-bus-env.test.ts
  • src/main/pty/dbus-session-bus-env.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 8 remain after this review.

Comment thread src/main/pty/dbus-session-bus-env.test.ts

@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 (c95d446), this run reviewed commit 069ff8d:

  • Reused the durable-scope bus resolver — repairDisabledSessionBusEnv now calls resolveUserRuntimeDir (and imports CANONICAL_USER_RUNTIME_DIR) instead of its own existsSync path check, so it prefers the per-UID bus, falls back to XDG_RUNTIME_DIR, and uses an isSocket() probe. This addresses the remaining inline thread's stale-path weakness and adds the XDG fallback.
  • Covered the local PTY lane — buildLocalPtySpawnEnvironment now calls repairDisabledSessionBusEnv, closing the fallback-lane gap raised as a body concern in the initial review.
  • Rewrote the helper tests against real Unix sockets — the previous socketExists seam is gone; cases now bind a real listener and exercise the injected platform/runtime-dir seams, including a plain-file-at-bus case that proves isSocket().
  • Added per-path wiring and daemon-launch tests — a mocked-helper wiring suite asserts each of the three spawn paths hands on the repaired address, and daemon-launched-child-spawn.test.ts covers both the direct forkProcess and systemd-run --scope arms.

I ran the four related suites (dbus-session-bus-env, dbus-session-bus-env-wiring, daemon-launched-child-spawn, daemon-cgroup-scope: 39 pass) and typecheck:node (clean). The prior inline thread is addressed and resolved.

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

oliver-mee and others added 4 commits September 28, 2026 09:52
Headless serve inherits Chromium's DBUS_SESSION_BUS_ADDRESS=disabled: marker. Add a Linux-only helper that swaps the exact marker for the user bus socket when that socket exists, and leaves every other value alone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Apply the repair to the daemon launch env, which feeds both the direct fork and the systemd-run --user --scope arm, and to the daemon PTY env. With the marker left in place, systemd-run --user fails with "Connection refused" and the scoped launch falls back to the unscoped path.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…pawn path

Resolve the bus with the resolver the durable daemon scope already uses (isSocket check, per-UID runtime dir first, XDG_RUNTIME_DIR as fallback) instead of a second existsSync check against the per-UID path. Repair the local, non-daemon PTY env too, which runs the same scrubbers as the daemon path.

Tests now exercise the helper against real sockets through injected platform and runtime-dir seams, and wiring tests fail if any of the three spawn paths stops calling it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Skip Unix-socket fixtures on Windows, where filesystem IPC paths are not valid. Document the test doubles to satisfy the changed-function docstring check.
@oliver-mee
oliver-mee force-pushed the fix/linux-session-bus-env branch from a8d0c42 to d7db8d3 Compare September 28, 2026 02:02
@oliver-mee

Copy link
Copy Markdown
Contributor Author

Rebased onto current main at 0b2dd0a99b (v1.4.214-122-g0b2dd0a99b, "fix(runtime): let the phone end a terminal stream by the request that opened it (#23006)") to clear the new conflicts.

Two conflicts, both from changes that landed next to ours:

  • local-pty-spawn-environment.ts: fix(pi): isolate status ownership in new terminals #22717 added stripPiProcessOwnerEnv. Both imports and both calls are kept, with the bus repair still running after the AppImage env cleanup.
  • pty-subprocess/spawn-environment.ts: main now has its own docstring on createDaemonPtyEnvironment, so I kept that one and dropped the docstring this branch had added. The Windows test-guard commit now only touches tests.

The import in dbus-session-bus-env.ts is also reformatted to match the current oxfmt config.

Checks after the rebase, on Linux arm64:

  • The four session-bus suites pass: dbus-session-bus-env, dbus-session-bus-env-wiring, daemon-launched-child-spawn and daemon-cgroup-scope.
  • Across src/main/{daemon,providers,pty}, the branch fails the same 14 files as a clean main checkout on this host (native node-pty and Windows-only suites, since deps were installed with --ignore-scripts). There are no new failures, and the branch adds 12 passing tests.
  • typecheck:node, oxlint and oxfmt --check are clean on the changed files.

Still reproduces on v1.4.215 headless serve. Shells spawned by the runtime inherit DBUS_SESSION_BUS_ADDRESS=disabled:, and at startup the daemon logs durable cgroup-scope launch failed, retrying without cgroup isolation after Failed to connect to bus: Connection refused. That is the systemd-run --user --scope path this PR also repairs. A full end-to-end serve run from a build of this branch is still not done.

@oliver-mee

Copy link
Copy Markdown
Contributor Author

Closing as superseded by #26306, whose session-bus fix was incorporated into #26483. Issue #21119 is already closed as completed. That implementation removes Chromium's exact disabled marker at daemon and PTY boundaries, so rebasing this older alternative would duplicate the landed fix.

@oliver-mee oliver-mee closed this Oct 18, 2026
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.

[Bug]: Headless serve propagates disabled session bus into all terminals and agents

1 participant