Skip to content

fix(cli): never resolve a proxy connection through the machine-wide session pointer - #4159

Merged
thymikee merged 1 commit into
mainfrom
maint/3931-leased-session-no-active-fallback
Nov 2, 2026
Merged

thymikee merged 1 commit into
mainfrom
maint/3931-leased-session-no-active-fallback

Conversation

@thymikee

@thymikee thymikee commented Nov 2, 2026 •

Copy link
Copy Markdown
Member

Summary

Without --session, a command used .active-session.json, so agent A's snapshot ran on agent B's proxy lease. Amended rule for #3931:

  • On a leased connection (any leaseProvider, including a remote config that sets one), every command needs an explicit session from --session, AGENT_DEVICE_SESSION, or config. That covers connect, open, open --remote-config, batch, close, disconnect, and connection status.
  • Otherwise the command fails with INVALID_ARGS, reason REMOTE_SESSION_REQUIRED, using the parser's lease provider. No second profile read and no lease work happen first.
  • Nothing mints a session. Each session on a shared leased profile gets a client and run derived from its name, so two sessions never share a lease. A host-issued lease keeps its scope.
  • A named session never reads the pointer. Unnamed non-leased flows are unchanged.

Behavior change: unnamed CI flows on lease providers now fail. Migration: set AGENT_DEVICE_SESSION=<name> per job before connect. Migrated here: the BrowserStack smoke test (asserting the refusal), help remote, the provider docs, and the Revyl, TestMu and proxy READMEs.

Direct daemon RPC callers are not covered here; tracked in #4171.

Fixes #3931

Validation

  • Gate 355a90b9bb: passed, exactCommit=true.
  • New cli-leased-session.test.ts (8 tests) covers:
    • two agents on one proxy, and on one shared profile, replayed against a real daemon;
    • a single profile read;
    • unnamed leased commands refused with no RPC;
    • a Limrun collision;
    • named sources;
    • a macos-app host lease.
  • No device run (CLI-only).

🤖 Generated with Claude Code

@vercel

vercel Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
agent-device-website Ready Ready Preview Nov 2, 2026 11:43am UTC

Request Review

@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Size Report

Metric Base Current Diff
Installed (including dependencies) 5.69 MB 5.69 MB +2.2 kB
Package (unpacked) 5.69 MB 5.69 MB +2.2 kB
Package (download) 1.73 MB 1.73 MB +753 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 27.3 ms 27.5 ms +0.2 ms
CLI --help 55.1 ms 57.0 ms +1.9 ms

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 11 files

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread src/cli.ts Resolved
Comment thread src/cli.ts Resolved
@apex-sentinel-staging

apex-sentinel-staging Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Apex Sentinel

Verdict: approve

Since the last review: 3 new · 1 still open · 6 no longer raised

4 finding(s): 1 minor, 3 nit. Verdict: approve.

🟡 Minor (1)

Where Finding
src/cli.ts:376 Bare connect is refused for a missing session before a missing plugin is named
🔵 Nit (3)
Where Finding
packages/remote/src/remote-connection-state.ts:371 🆕 sessionLeaseScope truncates clientId by the run-id limit, not the client-id limit
src/cli/commands/connection-runtime.ts:199 🆕 createRemoteConnectionStateFromFlags throws before the session check it now performs first
src/cli/commands/connection-runtime.ts:204 🆕 Comment asserts --run-id is shared, but scopeConnectProfile rewrites it too

Reply under an inline comment with @apex-sentinel-staging note ... or @apex-sentinel-staging pivot ... to re-check that finding.
Comment @apex-sentinel-staging review on this conversation to run a full review.

Earlier reviews

Review of 015a8070 · 02.11.2026 11:27 UTC

Apex Sentinel

Verdict: request_changes

Since the last review: 6 new · 1 still open · 8 no longer raised

7 finding(s): 1 major, 3 minor, 3 nit. Verdict: request_changes.

Required fixes

  • connect without a session is refused before provider resolution, so missing/unknown-provider guidance is replaced by REMOTE_SESSION_REQUIRED

🟠 Major (1)

Where Finding
src/cli.ts:377 🆕 connect without a session is refused before provider resolution, so missing/unknown-provider guidance is replaced by REMOTE_SESSION_REQUIRED

🟡 Minor (3)

Where Finding
packages/remote/src/remote-connection-state.ts:377 --remote-config with an unreadable or invalid profile now aborts every command instead of only the commands that previously loaded it
packages/remote/src/remote-connection-state.ts:341 🆕 The dropped allowActiveFallback also removes disconnect's pointer fallback when --remote-config is passed
packages/remote/src/remote-connection-state.ts:420 🆕 Leased-connection detection checks only leaseProvider; a stored connection or profile with a host-allocated leaseId/leaseBackend but no leaseProvider still resolves through the machine-wide pointer
🔵 Nit (3)
Where Finding
packages/remote/src/remote-connection-state.ts:408 🆕 remoteSessionRequiredError repeats --session and AGENT_DEVICE_SESSION in both message and hint
src/cli.ts:377 🆕 connect refusal echoes an unvalidated positional as details.leaseProvider
src/cli.ts:377 🆕 requireLeasedRemoteConfigSession parses the remote config a second time
Review of c093d1cb · 02.11.2026 11:00 UTC

Apex Sentinel

Verdict: request_changes

Since the last review: 6 new · 3 still open · 6 no longer raised

9 finding(s): 1 major, 5 minor, 3 nit. Verdict: request_changes. reached max rounds (1).

Required fixes

  • Decide between: A session-less batch on a leased remote config allocates its lease under the caller's session name, bypassing the REMOTE_SESSION_REQUIRED rule / session-less open --remote-config <leased profile> writes a session record that batch then inherits, reusing the minted session and its lease

🟠 Major (1)

Where Finding
packages/remote/src/remote-connection-state.ts:397 🆕 A session-less batch on a leased remote config allocates its lease under the caller's session name, bypassing the REMOTE_SESSION_REQUIRED rule

🟡 Minor (5)

Where Finding
packages/remote/src/remote-connection-state.ts:348 🆕 requireLeasedConnectionSession throws before the requested remote-config/profile path check, so disconnect and connection status can abort on an unreadable profile
packages/remote/src/remote-connection-state.ts:371 🆕 The 128-char run-id cap truncates profile.runId silently
packages/remote/src/remote-connection-state.ts:394 --remote-config with a lease the host already allocated is refused for open/connect, closing the profile-only recovery path for a materialization that never persisted (#​4135)
src/cli.ts:790 🆕 session-less open --remote-config <leased profile> writes a session record that batch then inherits, reusing the minted session and its lease
src/cli/commands/connection.ts:441 🆕 Two session-less open --remote-config runs write two records for the same profile, so the profile can no longer be closed or disconnected
🔵 Nit (3)
Where Finding
packages/remote/src/remote-connection-state.ts:371 MAX_RUN_ID_LENGTH duplicates the daemon's run-id limit and only its length is enforced, not its charset
packages/remote/src/remote-connection-state.ts:396 🆕 The runId the session-less open invents is built from one side of the flag/profile pair
src/cli/commands/connection-context.ts:44 connection status reports Session: <name> (docs say --json carries it) that no command accepts as --session
Review of 4c904014 · time not recorded

Apex Sentinel

Verdict: approve

Since the last review: 7 new · 2 still open · 8 no longer raised

9 finding(s): 1 minor, 8 nit. Verdict: approve. reached max rounds (1).

🟡 Minor (1)

Where Finding
packages/remote/src/remote-connection-state.ts:381 🆕 Session-less open --remote-config leaks a fresh connection session and lease on every invocation; nothing ends them
🔵 Nit (8)
Where Finding
packages/remote/src/remote-connection-state.ts:348 🆕 requireLeasedConnectionSession throws before the remote-config mismatch check, so --remote-config now aborts on another session's leased connection
packages/remote/src/remote-connection-state.ts:371 MAX_RUN_ID_LENGTH duplicates a daemon limit no shared contract enforces
packages/remote/src/remote-connection-state.ts:287 🆕 resolveRemoteConnectionDefaults doc describes a session fallback it never performs
packages/remote/src/remote-connection-state.ts:374 🆕 A session-less open --remote-config rewrites the machine-wide active-session pointer with a throwaway session that nothing ever removes
packages/remote/src/remote-connection-state.ts:415 🆕 The REMOTE_SESSION_REQUIRED hint's second option does not work for a stored leased connection
src/cli.ts:786 CONNECTION_STATE_COMMANDS is declared below the function that reads it
src/cli/commands/generic.ts:44 🆕 withLeasedOpenFacts copies every command's leveled payload even when it adds nothing
src/cli/commands/generic.ts:96 🆕 open now injects a connectionSession key into its own result data, colliding with the daemon's session field

@apex-sentinel-staging apex-sentinel-staging Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Apex Sentinel: 6 new findings on 1ed49c0. The verdict and the full list are in the summary comment.

Comment thread src/cli.ts Resolved
Comment thread src/cli.ts Resolved
Comment thread src/cli.ts Resolved
@thymikee
thymikee force-pushed the maint/3931-leased-session-no-active-fallback branch 2 times, most recently from bcb3708 to 2d973be Compare November 2, 2026 08:49

@apex-sentinel-staging apex-sentinel-staging Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Apex Sentinel: 8 new findings and 1 moved to a new line on 2d973be. The verdict and the full list are in the summary comment.

Comment thread src/cli.ts Resolved
Comment thread src/cli.ts Resolved
Comment thread src/cli.ts Resolved
Comment thread src/cli.ts Resolved
@thymikee
thymikee force-pushed the maint/3931-leased-session-no-active-fallback branch from 2d973be to 2a6ff1e Compare November 2, 2026 09:23

@apex-sentinel-staging apex-sentinel-staging Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Apex Sentinel: 8 new findings on 2a6ff1e. The verdict and the full list are in the summary comment.

Comment thread src/cli/commands/connection.ts Resolved
Comment thread src/cli.ts Resolved
@thymikee
thymikee force-pushed the maint/3931-leased-session-no-active-fallback branch 2 times, most recently from dfc1635 to 4fdf73b Compare November 2, 2026 10:08

@apex-sentinel-staging apex-sentinel-staging Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Apex Sentinel: 9 new findings on 4fdf73b. The verdict and the full list are in the summary comment.

Comment thread src/cli/commands/generic.ts Resolved
Comment thread src/cli.ts Resolved
Comment thread src/cli.ts Resolved
@thymikee
thymikee force-pushed the maint/3931-leased-session-no-active-fallback branch from 4fdf73b to 4c90401 Compare November 2, 2026 10:36

@apex-sentinel-staging apex-sentinel-staging Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Apex Sentinel: 7 new findings on 4c90401. The verdict and the full list are in the summary comment.

Comment thread src/cli/commands/generic.ts Resolved
Comment thread src/cli/commands/generic.ts Resolved
@thymikee
thymikee force-pushed the maint/3931-leased-session-no-active-fallback branch from 4c90401 to c093d1c Compare November 2, 2026 10:50

@apex-sentinel-staging apex-sentinel-staging Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Apex Sentinel: 1 new finding on c093d1c. The verdict and the full list are in the summary comment.

@thymikee
thymikee force-pushed the maint/3931-leased-session-no-active-fallback branch from c093d1c to 015a807 Compare November 2, 2026 11:16
Comment thread src/cli.ts Resolved

@apex-sentinel-staging apex-sentinel-staging Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Apex Sentinel: 2 new findings on 015a807. The verdict and the full list are in the summary comment.

Comment thread src/cli.ts Resolved
Comment thread src/cli.ts Resolved
Without --session, a command fell back to .active-session.json, which names
whichever connection saved last on the machine. With several agents on one
proxy, agent A's next command ran on agent B's lease.

On a leased connection (any lease provider: proxy, a device cloud, a provider
plugin, or a remote config that sets leaseProvider), every command now needs
an explicit session from --session, AGENT_DEVICE_SESSION, or config. That
covers connect, open, open --remote-config, batch, close, disconnect, and
connection status. Without one, the command is refused with INVALID_ARGS,
reason REMOTE_SESSION_REQUIRED, using the lease provider the parser already
read, before any profile is resolved again or any lease allocated. A named
session never falls back to the pointer. Each session on a shared leased
profile allocates under a client and run derived from its name, so two
sessions never share a lease and one session finds its own again; a
host-issued lease keeps its assigned scope. Unnamed non-leased flows are
unchanged.

Fixes #3931

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@thymikee
thymikee force-pushed the maint/3931-leased-session-no-active-fallback branch from 015a807 to 355a90b Compare November 2, 2026 11:43
Comment thread src/cli.ts Resolved

@apex-sentinel-staging apex-sentinel-staging Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Apex Sentinel: 2 new findings on 355a90b. The verdict and the full list are in the summary comment.

Comment thread src/cli/commands/connection-runtime.ts Resolved
Comment thread src/cli/commands/connection-runtime.ts Resolved
@thymikee

thymikee commented Nov 2, 2026

Copy link
Copy Markdown
Member Author

The code looks good at 355a90b. I found no problems in the code, and there are no conflicts. The Smoke Tests job failed in the iOS phase: record stop hit xcrun timed out after 30000ms while xcrun swiftc compiled the video validator. That route is a local simulator with a named session, and this change only reads the named session's absent state file there, so the failure looks unrelated. Please rerun the job and confirm it goes green before merge.

Not blocking, and you can take or leave these: (1) readRequestedConnectionState in connection.ts re-implements the pointer selection that selectRemoteConnectionState owns, so the active-session pointer has two readers and two copies of the leased guard. Exporting selectRemoteConnectionState with an optional remote config and calling it from there would leave one reader. (2) The guard at connection.ts:471 fires on the production route only for an unnamed connection status without --remote-config, and no test covers that. Deleting the guard would leave every test green. Adding ['connection', 'status'] to the unnamed loop in the first test, asserting REMOTE_SESSION_REQUIRED, would cover it. (3) 'a refused leased remote config is resolved only once' asserts count <= 1, which still passes if the mock stops intercepting the import. count === 1 is stricter. (4) The scopeConnectProfile comment says a provider-generated profile already derives client and run from the session. resolveCloudConnectProfile keeps a control-plane runId and clientId when the cloud returns them (cloud-profile.ts:55-56), so two cloud connect sessions can bind the same run. Either fix the comment or apply sessionLeaseScope there, after confirming the cloud bridge accepts a suffixed runId. (5) The rule that a --run-id equal to the profile's runId is scoped per session, and any other is kept, is written in both connection-runtime.ts:205 and connection.ts:329. Letting sessionLeaseScope take the requested runId and return the effective one would keep it in one place. (6) The help and docs say a leased connection refuses 'every command on' it. An unnamed local command such as snapshot or devices is also refused while another agent's leased connection is the machine-wide pointer. One added sentence in help remote and remote-proxy.md would say so, and tell users to name a session for local work on a shared host.

I did not run the gate or the new tests locally, and I judged the regression by reading the pre-change code. No live proxy run checked that a real gateway accepts the session-suffixed runId and clientId. I could not check whether the cloud control plane returns clientId as well as runId. MCP and Node SDK callers go straight to the daemon and never read the pointer, and #4171 tracks daemon-side refusal.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Nov 2, 2026
@thymikee
thymikee merged commit 7b3f63f into main Nov 2, 2026
21 of 22 checks passed
@thymikee
thymikee deleted the maint/3931-leased-session-no-active-fallback branch November 2, 2026 13:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Proxy leases: without --session, parallel agents pick up each other's session after open

1 participant