fix(server): agent sessions honour per-project device and browser access#31412
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.
ProjectServiceis now a required dependency ofProviderSessionManager, 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.layerProductionprovideslayerProjectServiceonce, 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 aProjectServicemock whosegetByIdreturns 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 overrideinapps/server/src/orchestration-v2/runtimeLayer.test.tsbuilds the realRuntimeLayer.layerProduction, with global access off and a project override on, and asserts that the thread's MCP credential is issued withdevice.- 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.
- Before the fix (42c1d80fc6):
runtimeLayer.test.ts,ProviderSessionManager.test.ts,ProjectSettingsUpgrade.integration.test.ts,DelegatedCompletionDelivery.test.ts,ProjectService.test.ts,ManagedProjectFolders.test.tsand the replay-harness consumers (ThreadStop,ThreadLaunchService,Orchestrator.control-reads,RemoteDelegatedTask) all pass.vp run check:changedpasses: 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:
| 1 | main @ 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 | |
| 11 | fix/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
prepareMcpSessionpath.
AI-assisted; verified with the tests and tools listed above.
🤖 Generated with Claude Code
Checks
Added 2 commits
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.
Thread transfer impact
✅ Thread transfer remains within every enforced ceiling.
| Provider | Metric | Main baseline | This PR | Impact | PR ceiling | |
|---|---|---|---|---|---|---|
| Codex | Total thread wire | 5.0 KiB | 5.0 KiB | 0 B (0.0%) | 6.8 KiB | ✅ |
| Codex | Thread snapshot wire | 3.8 KiB | 3.8 KiB | 0 B (0.0%) | 4.9 KiB | ✅ |
| Codex | Live turn WebSocket wire | 1.2 KiB | 1.2 KiB | 0 B (0.0%) | 2.0 KiB | ✅ |
| Codex | Live turn WebSocket decoded | 21.5 KiB | 21.5 KiB | 0 B (0.0%) | 24.4 KiB | ✅ |
| Codex | Live turn messages | 1 | 1 | 0 (0.0%) | 8 | ✅ |
| Claude | Total thread wire | 5.0 KiB | 5.0 KiB | 0 B (0.0%) | 6.8 KiB | ✅ |
| Claude | Thread snapshot wire | 3.8 KiB | 3.8 KiB | 0 B (0.0%) | 4.9 KiB | ✅ |
| Claude | Live turn WebSocket wire | 1.2 KiB | 1.2 KiB | 0 B (0.0%) | 2.0 KiB | ✅ |
| Claude | Live turn WebSocket decoded | 21.9 KiB | 21.9 KiB | 0 B (0.0%) | 24.4 KiB | ✅ |
| Claude | Live turn messages | 1 | 1 | 0 (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.
No actionable comments were generated in the recent review.
Walkthrough
-
The provider session manager now requires
ProjectServiceto 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.tsProviderSessionManagernow requiresProjectServiceand 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.tsThe production layer provides ProjectServicein 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
ProviderSessionManagercannot accessProjectService.
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 - pingdotgg/t3code#23085 — Reports that project browser access overrides are not resolved when
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 name Status Explanation Title check Passed The title clearly summarizes the main change: per-project device and browser access for agent sessions. Description check Passed 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 i… Linked Issues check Passed Issue #31394requires new agent sessions to use device tools when Agent device access is enabled.ProviderSessionManagernow requiresProjectServiceand resolves the thread's project override thro…Out of Scope Changes check Passed The ProviderSessionManagerdependency andRuntimeLayerwiring 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
#31394requires new agent sessions to use device tools when Agent device access is enabled.ProviderSessionManagernow requiresProjectServiceand resolves the thread's project override through it.RuntimeLayer.layerProductionprovides the service around the production graph. The regression test builds that graph and checks that a project override grants thedevicecapability. The PR reports that the test passes.
Full details: Out of Scope Changes check
-
Explanation
The
ProviderSessionManagerdependency andRuntimeLayerwiring 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
- Mode: Continue automatically
- Reviewed files: 5
- Charged: $1.25
- View usage details
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
-
- Source mode: Auto
- Repository: pingdotgg/t3code
- Branch: fix/issue-31394
- File: .coderabbit.config.ts
- Commit: d6dfd75
- Layers: Repository TS config
- Review profile: CHILL
- Plan: Team
- Run ID:
49d736b0-57a4-4df3-8db2-e557d9b8e909
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.tsapps/server/src/orchestration-v2/ProviderSessionManager.tsapps/server/src/orchestration-v2/runtimeLayer.test.tsapps/server/src/orchestration-v2/runtimeLayer.tsapps/server/src/orchestration-v2/testkit/ProviderReplayHarness.ts
Limit details: You’ve used all 10 included reviews currently available.
External image Recent review info
-
External image Run configuration
-
- Source mode: Auto
- Repository: pingdotgg/t3code
- Branch: fix/issue-31394
- File: .coderabbit.config.ts
- Commit: d6dfd75
- Layers: Repository TS config
- Review profile: CHILL
- Plan: Team
- Run ID:
49d736b0-57a4-4df3-8db2-e557d9b8e909
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.tsapps/server/src/orchestration-v2/ProviderSessionManager.tsapps/server/src/orchestration-v2/runtimeLayer.test.tsapps/server/src/orchestration-v2/runtimeLayer.tsapps/server/src/orchestration-v2/testkit/ProviderReplayHarness.ts
Limit details: You’ve used all 10 included reviews currently available.
-
Added 2 commits
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.
- Enable Device support; leave global Agent device access off and Agent browser access on.
- Project A overrides device access on and browser access off. Project B inherits global settings.
- Start a new session in A and request
device_list({}). - Start a new session in B and request
preview_status({}).
| Before | After |
|---|---|
| d6d1763d41 | d6dfd757fe |
| Recording | Recording |
| Device access-disabled error; browser capability denied. | Device host listing returned; browser status returned successfully. |
| Actual tool responses | Actual 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.