Repository navigation
Conversation
Size Report
Startup median (7 runs, lower is better):
|
There was a problem hiding this comment.
1 issue found across 25 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/cli/commands/exec.ts">
<violation number="1" location="src/cli/commands/exec.ts:139">
P2: Relative Windows executables such as ` .\gradlew.bat` are not resolved because this check recognizes only `/`; the spawn helper then rejects the backslash path. Recognize both path separators before calling `path.resolve`.</violation>
</file>
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
|
There was a problem hiding this comment.
All reported issues were addressed across 6 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
|
The exec wrapper can die on terminal hangup while the build it wraps keeps running, so the device claim does not last as long as the command (reviewed at 06e8600). The child is spawned with The rule to satisfy: while the child group is alive, every terminating signal the wrapper can catch is forwarded to the group, and the wrapper releases the claim only after the child exits. Could I did not run the SIGHUP behavior. It follows from Node's The live simulator run was at 55beffd. The later changes in The failing checks (Lint/Typecheck, Repo Guards, Integration, both Smoke lanes) all fail while loading |
06e8600 to
2665364
Compare
There was a problem hiding this comment.
All reported issues were addressed across 7 files (changes from recent commits).
You’re at about 90% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
|
Addressed in 2665364 (+ 5509d9a for the Windows test skip), rebased onto main ( Blocking (terminal hangup). Small items. The dead Live run at the final head (through Unverified side effects — recorded under "Not verified" in the PR body: the child has no controlling terminal, so The CI-red checks you listed are the #3717/#3720 builder gap on main; this head rebases over #3740, so they should clear. |
|
This PR is ready at 5509d9a. The hang-up (SIGHUP) problem from the earlier review at 06e8600 is fixed, and a real-process test now covers it. Not blocking, and fine to take or leave: the comment at src/cli/commands/exec.ts:244 repeats the one at :248-250, so you can delete the first copy; and no test sends SIGQUIT, so you could assert that One open inline thread still applies in spirit, but it is fixed at this head, so you can resolve it: the SIGHUP case is gated by All 24 checks pass at 5509d9a. The earlier red lanes came from the linux.gap builder gap on main, which #3740 fixed, and this head is rebased over #3740. There are no conflicts. Nothing else is needed before merge. I did not run the SIGHUP test or the old-code mutation here. I reasoned that the test fails before the fix from the 06e8600 handler set and Node's default SIGHUP behavior. I also did not reproduce the live simulator run at the final head (SIGHUP exit code 129, empty claims store, child gone), so that result is the author's report. I did not check two side effects of the detached child having no controlling terminal, |
|
The code verdict for 5509d9a is unchanged: clean. The branch now conflicts with main, so I removed |
49218df to
0d139bc
Compare
|
Rebased onto Conflict resolution. The real conflict was in the claim record: main's schema v3 (#3345 work) made the owner kind a required Check you asked for — the claim stays held for the command's lifetime after the merge. Re-ran the fence at The two notes from your 22:45 verdict are in too (top commit): the duplicated release comment at |
|
The code is clean at 0d139bc, and the conflict from the earlier review (5509d9a) is resolved. All 24 of 24 checks pass at this commit, including the claim tests in daemon-contracts and src/cli/commands/tests/exec.test.ts that exercise the changed records. Required evidence is satisfied. Not blocking: The cubic-dev-ai P2 thread about the SIGHUP test does not apply any more, so please resolve it: the case is gated by I did not run the wrapper or exec tests locally, so regression validity comes from reading the code path. I also did not reproduce the live simulator fence run at 0d139bc (DEVICE_IN_USE while held, SIGTERM rc 143 with the store empty, re-acquire rc 0), so that result is your report. The 516-file delta stat includes upstream main commits outside the logical patch, and I did not review those. No further action is needed before a human merge review. |
#2922 removed CHANGELOG.md in favor of generated release notes, and #3593 recreated it. Since then it has made parallel PRs conflict (#3732 three times, #3738), and it adds nothing that the release workflow does not already produce with `gh release --generate-notes`. Delete it and add it to .gitignore, so `git add CHANGELOG.md` or `git add .` refuses it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#2922 removed CHANGELOG.md in favor of generated release notes, and #3593 recreated it. Since then it has made parallel PRs conflict (#3732 three times, #3738), and it adds nothing that the release workflow does not already produce with `gh release --generate-notes`. Delete it and add it to .gitignore, so `git add CHANGELOG.md` or `git add .` refuses it. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
Follow-up for human review: the code-readiness state is unchanged at We also investigated whether the Expo use case needs this command. Live checks on iOS Simulator confirmed that an ordinary agent-device session protects install → launch → testing: competing installs from another session, another daemon, and another workspace using the same session name were refused. Direct The existing Simlock integration provides another no- For product review, the remaining question is which external device operation cannot be replaced by build-only output plus existing install/open commands. The tested Expo workflow can use those commands, with Simlock allocating devices where pooling is needed. A documentation/help recipe may cover that case; a process-owned wrapper remains a separate decision for unavoidable external operations on fixed local devices. This is a product-scope question for the maintainer, rather than a newly found code regression. |
`agent-device exec --platform ios --udid <UDID> -- <cmd> [args...]` takes the host-global device claim on the wrapper process's own identity, runs the child with stdio inherit, forwards SIGINT/SIGTERM to the child's process group, exits with the child's code (128+signum for signal exits, 127 for a missing command), and releases the claim on every exit path. A killed wrapper's claim carries an explicit owner kind so the supersession proof never steals a live wrapper claim and every later acquirer reconciles a dead one as stale. - device-claims: acquireWrapperDeviceClaim records ownerKind 'wrapper' under a dedicated <stateDir>/exec claim dir; the session label exec:<command> is sanitized like session names. - device-claim-inspection: the daemon-supersession proof applies only to daemon-served claims (ownerKind absent); wrapper liveness stands. - device-claim-conflict moves to daemon-contracts so the wrapper throws the same typed refusal the daemon admission seam returns. - registry: new honest 'wrapper-exclusive' claim policy for the exec descriptor. - docs: commands.md and sessions.md claim sections, README claim paragraph.
The wrapper-claim label sanitizing reached host-kit/session-paths statically, growing three budgeted eager closures by one module each. Take the same safeSessionName through a function-scoped dynamic import, as the closure guard prescribes.
… docs - Forward every received signal to the child group; the first one still owns the exit code, so a child that traps or ignores SIGINT stays stoppable by a later SIGTERM (r4297377444). - Refuse to start on win32 with UNSUPPORTED_PLATFORM and reason unsupported-host-platform instead of half-supporting process-group signalling and backslash relative paths there (r4297377356, r4297377386); help and docs now say macOS and Linux hosts. - Help synopsis lists every accepted selector: --target and --ios-simulator-device-set were declared but missing (r4297377464). - Docs: README shows matching selectors fencing the same device the build targets (r4297377397); sessions.md names the local-only boundary (r4297377423), the attempted-release guarantee (r4297377435), the exact auto-reconcile condition with uncertain claims staying blocked (r4297377455); commands.md documents the reboot-takeover exception (r4297377408).
- README: the hosted-device sentence no longer covers `exec`; it holds claims for local devices only (r4297509439). - exec tests: the second-signal case pins listener restoration for both SIGINT and SIGTERM, so a leaked onSigint fails the run (r4297509474).
… build too The child leads its own session, so terminal hangup reached only the wrapper and its default action killed it before the release: the claim named a dead pid while the build kept mutating the device. attachSignalForwarding now owns one list of forwarded signals (SIGINT, SIGTERM, SIGHUP, SIGQUIT), registers and disposes every handler from it, and replays all signals received before the child existed. The claim is released only after the child exits. - exec tests: real-process SIGHUP case (fails on the previous code: wrapper killed, claim stranded, child alive) asserting release-before-exit, the child group stopping, and exit 129. - help and docs name the full forwarded set. - delete the dead outcome assignment in releaseExecDeviceClaim. - fix the stale device-claim-conflict.ts path in the help-conformance sample.
Signal disposition, process-group kill, and a real SIGHUP's default action are POSIX semantics, and exec refuses before claiming on a Windows host, so the signal cases would only time out waiting for the claim there. Gate all three from one IS_WINDOWS condition (r4298409165).
…pin every forwarded signal's cleanup - one copy of the leave-a-stale-claim note in releaseExecDeviceClaim (the unattributable branch below already carries it). - the SIGTERM case now snapshots process.listenerCount for every FORWARDED_SIGNALS entry and asserts all of them return to baseline, so a leaked SIGQUIT or SIGHUP handler fails here too. Rebase note: main's claim schema v3 (#3345) made the owner kind a required claimOwnerKind discriminant, so the wrapper kind rides that field instead of a parallel ownerKind, and the supersession guard checks claimOwnerKind !== 'wrapper'.
…s the file cap Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#3763 and #3798 made claim storage and recovery read an explicit host. exec now takes the host from the CLI entry like web does, and passes host.env and host.cwd to the claim store, the recovery gateway, device inventory, and the child process. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Rebased onto The rebase needed two fixes beyond the conflict-free replay:
The claim behaviour is unchanged. |
0d139bc to
1767adc
Compare
|
The PR is ready at 1767adc. Both the exec logic and the host.env plumbing look correct in the diff, and the earlier clean review at 0d139bc still holds because those commits are unchanged. The 12 earlier review threads were all resolved before that review, and none has reopened since. I ran no tests locally and judged the host.env change from the diff only. All 23 checks pass at 1767adc, and there are no conflicts. Not blocking, and you can take or leave it: in src/cli/commands/tests/exec.test.ts (https://github.com/callstack/agent-device/blob/1767adcc3d/src/cli/commands/__tests__/exec.test.ts#L79), createTestHostEnvironment() copies process.env by default and the assertions read the claim store through process.env, so a regression that reads process.env instead of host.env would still pass. Would one case that passes a custom env with its own claims directory, without touching process.env, and asserts the claim lands there cover it? Nothing in code is left before merge, so the PR now waits on maintainer review and merge. |
Summary
agent-device exec --platform ios --udid <UDID> -- expo run:ios --device <UDID>holds the host-global device claim for the whole lifetime of an external command, so builds that install through the platform tools directly are fenced against agent-device sessions on the same device. Fixes #3263.wrapper-exclusiveclaim policy; the registry never claims the wrapper goes through daemon admission.ownerKind: 'wrapper'; the daemon-supersession proof (which reads adaemon.jsonthat can only ever describe daemons) no longer steals live wrapper claims, and claims under a dedicated<stateDir>/execdir reconcile asowner-process-deadwhen the wrapper is killed.daemon-contractssoexecthrows exactly the refusal the daemon admission seam returns.execrefuses to start on win32 (UNSUPPORTED_PLATFORM,reason: unsupported-host-platform).commands.md,sessions.md, README (matching selectors fence the very device the build targets). No scope expansion beyond the claim-store owner-kind fix the wrapper needs.Validation
Tested at 0d139bc (review rounds 1–3, rebased onto
4a8abed7b9).pnpm check:affected --run --head 0d139bce0at the exact commit: all runnable checks passed (receipt.tmp/validation/run-XpKV1T/receipt.json).claimOwnerKinddiscriminant, so the wrapper kind now rides that field (claimOwnerKind: 'wrapper') instead of a parallel optionalownerKind, and the supersession guard checksclaimOwnerKind !== 'wrapper'. The wrapper-claim tests pin the fence after the merge: live wrapper claim not stealable from a registration, foreign acquire refused while the wrapper runs, release on exit.acquiredinstead ofconflict); with the forwarded-signal list narrowed back to SIGINT/SIGTERM, the real-SIGHUP case fails (claim stranded at a dead pid, child alive). Mutations (no release, no signal code, no forward, drop-second-signal) each fail their test for the named reason.pnpm typecheck,pnpm lint,check:layering,check:doc-prose, help-conformance, command-doc-coverage, fullpackages/daemon(2770 tests), daemon-contracts, command-registry, exec suites — green. The three POSIX signal cases share onetest.skipIf(IS_WINDOWS)condition.ExecIssue3263R3, iOS 27.0, created and deleted for the check; 5509d9a only adds a Windows test skip): duringexec … -- sleep 120,device statusshowedlive session=exec:sleepand a secondexecrefused withDEVICE_IN_USEnamingsession "exec:sleep"; SIGTERM → rc 143 and claims store empty; SIGHUP → rc 129, the childsleepin the wrapper's group was gone, and the claims store was empty once the wrapper exited; after exit a freshexecsucceeded (rc 0).ExecFenceR4, created and deleted for the check): status showedlive session=exec:sleep, a secondexecrefused withDEVICE_IN_USEnaming the holder, SIGTERM → rc 143 with the store empty, and a freshexecafter exit succeeded rc 0 — the conflict resolution keeps the claim held for the external command's lifetime./dev/tty(sudo, credential prompts) may fail — untested; Ctrl-Z stops the wrapper but not the child (job control for a session leader's detached child would need a terminal the child doesn't share).🤖 Generated with Claude Code