fix(server): agent sessions honour per-project device and browser access#31412

open4
main
fix/issue-31394
opened6d agobyJJulius Marminge
View on GitHubTwo-way sync enabledPR last imported 20h ago

Problem

Turning on Agent device access for a project in Settings > Devices doesn't reach new agent sessions: device_list returns "Agent device access is turned off for this environment…". On main @ 337c5858c3: turn global Agent device access off, scope Settings > Devices to a project and turn it on there, then start a new Codex thread in that project and call device_list. The thread gets the "turned off" error. As soon as any project has a device-access override, every thread loses device tools, whatever its own project or the global setting says. Browser overrides cause the same loss of browser tools.

Change

ProviderSessionManager read ProjectService with Effect.serviceOption. In the production layer graph, the shared session manager layer is first built through the ResourceCleanupService → scheduled task → thread management chain, before ProjectService is in context. The optional lookup therefore always came back empty. With an override present, agentAccessSettings treated the thread's project as missing and withheld access.

  • ProjectService is now a required dependency of ProviderSessionManager, so a build path without it fails typecheck instead of silently withholding access. The access rules are unchanged: an override whose project cannot be found still withholds access.
  • RuntimeLayer.layerProduction provides layerProjectService once, around the whole production graph. The three sibling layers that built their own copy alongside layers that need it (AgentSessionImporter, ThreadLaunch, ScratchThreadPromotion) now get it from that single provide.
  • Test harnesses that construct the manager directly (ProviderSessionManager.test.ts, ProviderReplayHarness.ts) get a ProjectService mock whose getById returns none, the same result the old missing-service path produced.

Scope and approval

Fixes pingdotgg/t3code#31394

Verification

  • New test production wiring gives a thread its project's agent device access override in apps/server/src/orchestration-v2/runtimeLayer.test.ts builds the real RuntimeLayer.layerProduction, with global access off and a project override on, and asserts that the thread's MCP credential is issued with device.
    • Before the fix (42c1d80fc6): AssertionError: the project's enableAgentDeviceAccess override must grant device tools; issued: orchestration, worktree, pull-requests, preview: expected false to be true
    • After the fix (1b78ffb9c3): passes.
  • runtimeLayer.test.ts, ProviderSessionManager.test.ts, ProjectSettingsUpgrade.integration.test.ts, DelegatedCompletionDelivery.test.ts, ProjectService.test.ts, ManagedProjectFolders.test.ts and the replay-harness consumers (ThreadStop, ThreadLaunchService, Orchestrator.control-reads, RemoteDelegatedTask) all pass.
  • vp run check:changed passes: format, lint, knip, typecheck of 3 packages, tests.
  • Runtime transcript from a dev server on a fresh database (T3CODE_DEV_SEED=0), Linux, real Codex sessions:
1main @ 337c5858c3
2- Global Agent device access on, no overrides: new Codex thread and new Claude thread -> device_list returns the host list.
3- Global off; Settings > Devices scoped to project "server", Agent device access on
4 (settings.json: projectSettingsOverrides["<project id>"].enableAgentDeviceAccess = true, same id as the thread's project_id).
5 New Codex thread in that project -> device_list ->
6 "Agent device access is turned off for this environment. Ask the user to turn on Agent device access in
7 Settings > Integrations > Devices; it applies when the agent session next starts, such as in a new thread."
8- Temporary log in the ProviderSessionManager layer constructor (not committed):
9 { hasProjectService: false, hasServerSettings: true }
10
11fix/issue-31394 @ 1b78ffb9c3, same settings
12- New Codex thread in that project -> device_list ->
13 {"hostStatuses":{},"hosts":[{"id":"local","label":"This machine","platforms":[...],"kind":"local"}],"devices":[],"open":[]}
  • Not checked: there are no visible UI changes, so there are no captures; no other provider (Cursor, Grok, OpenCode, Antigravity) was run at runtime, since they all receive capabilities through the same prepareMcpSession path.

AI-assisted; verified with the tests and tools listed above.

🤖 Generated with Claude Code

Checks

22 successful · 12 skipped
All checks passed
d6dfd75

Added 2 commits

  • fix(server): agent sessions honour per-project device and browser access1b78ffb
  • test(server): reproduce #31394 project device access lost in production wiring42c1d80
Synced from GitHub

Approvability

Verdict: Approved at d6dfd75

Macroscope's review found this PR approvable — The change ensures agent sessions receive the project service needed to honor existing per-project browser and device access settings. Existing fail-closed handling and capability checks remain in place, with no product-default changes in the assessed diff.

You can add or adjust custom eligibility rules. Learn more.

Synced from GitHub

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire5.0 KiB5.0 KiB0 B (0.0%)6.8 KiB✅
CodexThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiB✅
CodexLive turn WebSocket wire1.2 KiB1.2 KiB0 B (0.0%)2.0 KiB✅
CodexLive turn WebSocket decoded21.5 KiB21.5 KiB0 B (0.0%)24.4 KiB✅
CodexLive turn messages110 (0.0%)8✅
ClaudeTotal thread wire5.0 KiB5.0 KiB0 B (0.0%)6.8 KiB✅
ClaudeThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiB✅
ClaudeLive turn WebSocket wire1.2 KiB1.2 KiB0 B (0.0%)2.0 KiB✅
ClaudeLive turn WebSocket decoded21.9 KiB21.9 KiB0 B (0.0%)24.4 KiB✅
ClaudeLive turn messages110 (0.0%)8✅

Baseline: d6d1763 · PR result: d6dfd75 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Synced from GitHub

Explain Change →

No actionable comments were generated in the recent review.

Walkthrough

The provider session manager now requires ProjectService to look up project settings. Production wiring provides the service. A runtime-layer test checks that a project-specific device access override appears in issued session capabilities.

Changes: Provider session project access

Layer / File(s)Summary
Required project lookup and test fixtures
apps/server/src/orchestration-v2/ProviderSessionManager.ts, apps/server/src/orchestration-v2/ProviderSessionManager.test.ts, apps/server/src/orchestration-v2/testkit/ProviderReplayHarness.ts
ProviderSessionManager now requires ProjectService and uses it for project lookups. The test and replay harness provide a mock service when needed.
Production wiring and device capability test
apps/server/src/orchestration-v2/runtimeLayer.ts, apps/server/src/orchestration-v2/runtimeLayer.test.ts
The production layer provides ProjectService in its pipeline. A runtime-layer test checks that a project-specific device access override is reflected in issued MCP session capabilities.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Medium

Possibly related issues

  • pingdotgg/t3code#23085 — Reports that project browser access overrides are not resolved when ProviderSessionManager cannot access ProjectService.

Possibly related PRs

  • pingdotgg/t3code#33342 — Moves threads between projects, whose project association determines the overrides used for a provider session.

Suggested reviewers: t3dotgg, maria-rcks

Merge Risk: External image Minimal · up to d6dfd

The identified production and replay entrypoints have the required project service. No actionable merge risk remains after normal checks.

Pre-merge checks: Passed 4

Check nameStatusExplanation
Title checkPassedThe title clearly summarizes the main change: per-project device and browser access for agent sessions.
Description checkPassedThe description covers the problem, implementation, linked issue, and focused verification results. It names Claude Code but does not specify the model and harness as requested by the template; this i…
Linked Issues checkPassedIssue #31394 requires new agent sessions to use device tools when Agent device access is enabled. ProviderSessionManager now requires ProjectService and resolves the thread's project override thro…
Out of Scope Changes checkPassedThe ProviderSessionManager dependency and RuntimeLayer wiring changes directly fix #31394. The related browser-override behavior uses the same access-resolution path. Test-harness mocks support th…
Full details: Description check

Explanation

The description covers the problem, implementation, linked issue, and focused verification results. It names Claude Code but does not specify the model and harness as requested by the template; this is a minor omission.

Full details: Linked Issues check

Explanation

Issue #31394 requires new agent sessions to use device tools when Agent device access is enabled. ProviderSessionManager now requires ProjectService and resolves the thread's project override through it. RuntimeLayer.layerProduction provides the service around the production graph. The regression test builds that graph and checks that a project override grants the device capability. The PR reports that the test passes.

Full details: Out of Scope Changes check

Explanation

The ProviderSessionManager dependency and RuntimeLayer wiring changes directly fix #31394. The related browser-override behavior uses the same access-resolution path. Test-harness mocks support the new required dependency, and the production-layer test verifies the fix. No unrelated change is evident in the reviewed PR diff.

Finishing Touches

Generate docstrings

  • Commit to this branch
  • Create a new PR
Generate unit tests (beta)

  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Usage-based review receipt

Note

This review was completed with usage-based billing: files reviewed beyond your plan's included limits are billed at $0.25/file. View usage-based billing.

External image Recent review info

External image Run configuration

External image Commits

Reviewing files that changed from the base of the PR and between 1b78ffb9c3b9ecf4645852c8933e8e677840de87 and d6dfd757fe02e04d9f940539661155c3d936b0e6.

External image Files selected for processing: 5

  • apps/server/src/orchestration-v2/ProviderSessionManager.test.ts
  • apps/server/src/orchestration-v2/ProviderSessionManager.ts
  • apps/server/src/orchestration-v2/runtimeLayer.test.ts
  • apps/server/src/orchestration-v2/runtimeLayer.ts
  • apps/server/src/orchestration-v2/testkit/ProviderReplayHarness.ts

Limit details: You’ve used all 10 included reviews currently available.

Added 2 commits

  • Merge remote-tracking branch 'origin/main' into fix/issue-31394b607069
  • test(server): mock services main's production layer now needsd6dfd75

Evidence

Both sides: Linux, real development server, separate worktrees and fresh databases, identical synthetic seed, real Codex sessions, dark terminal capture at 1366×890. Settings were applied through the app's RPC services.

  1. Enable Device support; leave global Agent device access off and Agent browser access on.
  2. Project A overrides device access on and browser access off. Project B inherits global settings.
  3. Start a new session in A and request device_list({}).
  4. Start a new session in B and request preview_status({}).
BeforeAfter
d6d1763d41d6dfd757fe
RecordingRecording
Device access-disabled error; browser capability denied.Device host listing returned; browser status returned successfully.
Actual tool responsesActual tool responses

Seed/session driver · Recording script · Environment and instructions

Recordings show persisted server tool responses, with private metadata redacted. AI-assisted; verified with the tests and tools listed above.

Not shown:

  • Actual simulator/emulator control was not exercised: this environment has no local devices installed. The successful head response lists the local host and platform availability.
  • Browser navigation was not exercised; preview_status successfully returned the normal no-open-tab state on head.
  • Settings clicks, desktop/mobile clients, other providers, and scheduled/imported/delegated session paths were not exercised; this proof targets new Codex sessions through the real server.