Skip to content

Make remote workspaces interactive and stable - #42

Merged
ArtificialNotImbecile merged 24 commits into
mainfrom
codex/remote-session-workflow
Aug 21, 2026
Merged

Make remote workspaces interactive and stable#42
ArtificialNotImbecile merged 24 commits into
mainfrom
codex/remote-session-workflow

Conversation

@ArtificialNotImbecile

@ArtificialNotImbecile ArtificialNotImbecile commented Aug 21, 2026

Copy link
Copy Markdown
Owner

Summary

  • turn an empty remote workspace into an interactive draft: the first prompt atomically creates the managed Pi session, waits for agent_settled, reconciles the listing, and opens the returned transcript
  • expose clear running and explicit Stop states without aborting remote work during normal cleanup
  • keep connection health separate from session synchronization, remove the duplicate workspace refresh trigger, and keep Connected stable while the refresh button owns its spinner
  • place remote header controls above the shell drag strip so New session and Refresh accept real pointer clicks
  • publish pi-remote runtime/package 0.1.2 with the sessions.read and daemon status (daemonId/activeRpc) protocol required by remote transcripts and restart recovery

Regression coverage

  • renderer: empty-workspace draft/start/send flow, subsequent prompt, explicit Stop, refresh throttling, transcript reconciliation
  • main/unit: schema validation, create+prompt lifecycle ordering, pre-send subscription, settlement/disconnect handling, non-aborting cleanup, connection/sync split, single route refresh owner
  • E2E: the existing remote-profile restart case now performs a real pointer click on New session and verifies the prompt control
  • inventories: remote UI/workflow and harness map updated

Validation

  • npm.cmd run build
  • npm.cmd run test:unit: 23/23 suites
  • npm.cmd run test:renderer: 22 files / 176 tests
  • npm.cmd run harness:check
  • npm.cmd run harness:inspect
  • npm.cmd run harness:visual plus an off-screen real-profile screenshot review of the remote draft page
  • targeted Playwright remote profile/draft case: 1/1
  • full local Electron E2E: 94 passed before the installed Jasmine process prevented the focus-sensitive runner from registering Control+Shift+Space; the 3 startup-timing cases passed separately. CI must confirm the complete clean-session matrix.
  • real root@172.16.115.11:4560 main-service run with an isolated temporary profile/cwd and local mock provider: durable session, user turn, assistant turn, sessions.read, and status patches [ready] all passed; remote profile/session/mock/cwd cleanup verified
  • runtime 0.1.2 built with the Ubuntu 18.04 / glibc 2.27 WSL baseline and verified as 419eec35e672d098056279c4e4157cbaa10042b4f73ced8c571b04c8431e0835 (the 0.1.1 build and dry-run below were repeated for 0.1.2)
  • @jasmine-ai/pi-remote@0.1.2 dry-run package: 42 files, runtime verify passed
  • runtime prerelease: https://github.com/ArtificialNotImbecile/pi-desktop/releases/tag/pi-remote-runtime-0.1.2

Post-review fix

  • profile Stop now bounds its wait on the aborted operation: a detached monitor retrying an unreachable host can no longer park the stop request forever; the timeout reports an unconfirmed stop while the background retry continues (regression guard added in tests/unit/remote-sessions.mjs)

@ArtificialNotImbecile

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b2443a21aa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if (started) {
setTranscript(started.transcript);
setDrafting(false);
props.onSelectSession(started.session.sessionId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Ignore completed starts after leaving the workspace

When a user starts a first prompt and then navigates elsewhere through the still-active sidebar before it settles, this async handler survives the workspace change or component unmount and unconditionally invokes the captured onSelectSession. Completion therefore forces the user back to the newly created remote session, potentially long after they left; invalidate the request on scope change/unmount and gate this navigation on the original workspace still being active.

Useful? React with 👍 / 👎.

@ArtificialNotImbecile

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5a59393d41

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +55 to +59
useEffect(() => {
promptRequestRef.current += 1;
setDrafting(false);
setDraft("");
setOptimisticPrompt(null);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reset prompt flags when switching workspaces

When a prompt is running and the user navigates directly to another remote workspace or profile, this effect invalidates the old completion but clears only the draft-related state. starting, sendingSessionId, and stopping remain set, and the stale-request guard prevents the old completion from clearing them, so the new workspace's New session/composer controls remain disabled indefinitely and Stop may target the old session under the new profile. Reset these operation UI flags on scope changes and cover same-component workspace navigation as another variant of the reported lifecycle bug.

AGENTS.md reference: AGENTS.md:L20-L20

Useful? React with 👍 / 👎.

@ArtificialNotImbecile

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 299711c90a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/main/services/remoteSessionRun.ts Outdated
} finally {
settled?.cancel();
await settled?.promise.catch(() => {});
await port?.close({ abort: false }).catch(() => {});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve remote work after the prompt timeout

When a remote turn runs longer than 30 minutes, the waiter rejects, but this finally still calls port.close({ abort: false }). That is not a detach operation: PiRpcSessionPort.close invokes rpc.stop, and RemoteHostDaemon.stopRpc(false) ends the Pi process's stdin and sends SIGTERM after two seconds. Thus a long-running remote task is terminated even though the UI presents Stop as the explicit cancellation path; the timeout path should disconnect without stopping the remote RPC process.

Useful? React with 👍 / 👎.

@ArtificialNotImbecile

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3114b6871c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/main/services/remoteProfiles.ts Outdated
await this.refreshSessions(profile.id);
return await this.openSession(profile.id, sessionId, true);
} finally {
this.releaseOperation(profile.id, operation);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep detached prompts registered until they settle

When a prompt exceeds 30 minutes or its transport disconnects, the new detach path intentionally leaves the remote RPC process running, but this finally immediately removes its ActiveRemoteOperation. After the IPC request rejects, the renderer removes the running/Stop UI, and abortSession can no longer find the operation, so reconnecting or returning to the workspace cannot cancel work that is still consuming remote resources; a subsequent prompt may instead attach to the still-busy RPC process. Keep detached operations discoverable or reattachable until the daemon reports settlement, and cover the timeout/disconnect-to-Stop variants. This is fresh evidence beyond the earlier timeout finding because the new detach() fix preserves the process while making it untracked.

AGENTS.md reference: AGENTS.md:L20-L20

Useful? React with 👍 / 👎.

@ArtificialNotImbecile

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5f619c9ca8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

private readonly store: ProfileStore;
private readonly runtime: ManagedRemoteRuntime;
private readonly statuses = new Map<string, RemoteProfileStatus>();
private readonly activeOperations = new Map<string, ActiveRemoteOperation>();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restore running operations after restarting Jasmine

When Jasmine is exited and relaunched while a prompt is running, the SSH client disconnects but the remote daemon deliberately leaves its RPC process alive; this in-memory map is then recreated empty, so the relaunched renderer offers Send with no Stop control and reserveOperation accepts another turn for the same profile. Fresh evidence beyond the prior detach finding is that the final fix tracks reconnects only within this service instance and provides no startup discovery or persistence. Rehydrate the daemon's active session state on startup and cover the app-restart lifecycle variant.

AGENTS.md reference: AGENTS.md:L20-L20

Useful? React with 👍 / 👎.

Comment thread src/main/services/remoteProfiles.ts Outdated
Comment on lines +235 to +236
if (active?.port) await active.port.close({ abort: true }).catch(() => {});
if (active) this.releaseOperation(profile.id, active);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep the active operation reserved until profile stop settles

When Stop remote runtime is invoked from settings during a prompt, this releases the operation before the original startSession/promptSession request has reached its own cleanup. The running UI disappears and a new prompt can reserve the profile while the old handler may still proceed into refreshSessions/openSession, racing runtime.stop and potentially overwriting the final disconnected status with ready or failed. Mark the operation as stopping and wait for its request to settle before releasing it.

Useful? React with 👍 / 👎.

Comment thread src/main/services/remoteSessionRun.ts Outdated
Comment on lines +108 to +110
const replacement = await options.runtime.openSession(options.profile, {
sessionId: options.sessionId,
afterSeq: cutoff

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Detect a reset daemon event sequence during reconnect

When the SSH interruption includes a daemon restart or host reboot, cutoff belongs to the old daemon while the replacement daemon's sequence starts again at zero. Passing the old value as afterSeq and retaining it in waitForRemotePromptSettled causes every new agent_settled or exit event to be ignored as seq <= cutoff, leaving the operation in repeated 30-minute reconnect cycles even though the original process is already gone. Detect a daemon epoch/sequence reset and fail or reset the waiter, and add this reconnect variant to the regression coverage.

AGENTS.md reference: AGENTS.md:L20-L20

Useful? React with 👍 / 👎.

@ArtificialNotImbecile

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aaae4270db

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/main/services/remoteProfiles.ts Outdated
Comment on lines +475 to +476
await this.runtime.stop(profile);
this.releaseOperation(profile.id, operation);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep opening operations reserved until their handler settles

When Stop is clicked while startSession is still opening its SSH/RPC port, operation.port is null, so this branch stops the current daemon and immediately resolves active.done by releasing the reservation even though the original handler can continue into ensureDaemon/rpc.start. Another workspace can then reserve the profile and collide with that handler, and stopProfile's new await active.done does not actually wait for cleanup. Fresh evidence beyond the earlier profile-stop finding is that the newly added completion promise is resolved here before the originating request settles; let that request's finally own the release and cover this opening-port variant.

AGENTS.md reference: AGENTS.md:L20-L20

Useful? React with 👍 / 👎.

Comment thread src/main/services/remoteProfiles.ts Outdated
Comment on lines +469 to +470
if (operation.port) {
await operation.port.abort();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Release the operation after an abort transport failure

If the SSH transport fails while the user clicks Stop on an active port, port.abort() rejects here after abortRequested was set. The prompt handler classifies the same disconnect as detached and starts its monitor, but that monitor immediately skips its loop and declines to release because abortRequested is true; this branch also exits by throwing, leaving the profile permanently reserved in a reconnecting/stopping state until Jasmine restarts. Ensure this failure path reaches one owner that releases the operation, and add the disconnect-during-Stop regression variant.

AGENTS.md reference: AGENTS.md:L20-L20

Useful? React with 👍 / 👎.

@ArtificialNotImbecile

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 91616fa967

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 692 to 693
child.kill();
await egress?.close();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep client-proxy egress alive while detached

When a client-proxy prompt times out or its control SSH connection drops, the new detach path reaches this finally and closes the EgressBroker, which terminates both the reverse tunnel and local gateway. The detached RPC process therefore loses its only provider egress, while monitorDetachedOperation merely inspects the daemon and never recreates the broker, so the work advertised as continuing remotely can fail or remain stuck. Preserve or re-establish the egress lease while detached and cover the client-proxy timeout/disconnect variants.

AGENTS.md reference: AGENTS.md:L20-L20

Useful? React with 👍 / 👎.

setDrafting(false);
props.onSelectSession(started.session.sessionId);
}
if (!result) setDraft(text);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Do not restore prompts that already completed remotely

When the remote turn settles successfully but the subsequent refreshSessions or openSession reconciliation fails, both hook methods return null, so this branch restores the text as though it was never sent. Retrying then submits the same turn again—or creates a second session for a failed first-session reconciliation—even though the original work is already durable remotely. Distinguish pre-send failure from post-settlement sync failure and cover both start and existing-session variants.

AGENTS.md reference: AGENTS.md:L20-L20

Useful? React with 👍 / 👎.

@ArtificialNotImbecile

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 336b665791

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

checkedAt: new Date().toISOString(),
busy: false
});
this.recoverActiveOperation(profile, runtimeInfo);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Recover detached operations during service startup

When Jasmine restarts during a remote prompt, navigation initializes to newChat, so the route effect does not refresh any remote profile; this recovery hook runs only after refreshSessions, which requires the user to open or expand that profile. Until then the operation is unreserved, and a client-proxy turn has no replacement egress tunnel after the old process exits, so it can fail or collide with another submission. Fresh evidence beyond the earlier restart finding is that the new recovery implementation is still reachable only from this interactive refresh path rather than service initialization.

AGENTS.md reference: AGENTS.md:L20-L20

Useful? React with 👍 / 👎.

Comment thread src/main/services/remoteSessionRun.ts Outdated
Comment on lines +34 to +35
await port.prompt(text);
await callbacks.onPromptAccepted?.();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Record acceptance when the daemon delivers the prompt

If the transport drops after rpc.send has written the prompt into the remote process but before Pi's command response reaches this await, port.prompt() rejects and onPromptAccepted never runs even though the daemon may report the RPC as busy and continue executing it. RemoteProfileService consequently rethrows the detached failure, the renderer restores the draft, and retrying after the monitor releases the operation can submit the same work twice. Fresh evidence beyond the post-settlement synchronization finding is the unhandled gap between the daemon's delivery acknowledgement and Pi's later RPC response; mark the submission accepted at the former boundary.

AGENTS.md reference: AGENTS.md:L20-L20

Useful? React with 👍 / 👎.

Comment on lines +804 to +806
info = await this.runtime.inspectRuntime(profile, { install: false });
} catch (error) {
if (!isRetryableError(error)) throw error;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Retry transient daemon-proxy failures while monitoring

When a second SSH interruption occurs during detached polling after the runtime probe succeeds, inspectRuntime can wrap the failed daemon proxy connection as daemon-proxy-failed, whose retryable flag defaults to false. This branch therefore throws instead of polling again, and the monitor's finally releases the reservation and retained egress resources while the remote RPC can still be busy. Fresh evidence beyond the earlier detached-registration finding is that the new monitor classifies its own transient connection failure as terminal; normalize daemon connection failures as retryable or keep the operation reserved until daemon state is known.

AGENTS.md reference: AGENTS.md:L20-L20

Useful? React with 👍 / 👎.

@ArtificialNotImbecile

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dc88a7f69b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/main/services/remoteProfiles.ts Outdated
* says so, and this is what makes that promise true.
*/
async removeProfile(profileId: string): Promise<void> {
if (this.activeOperations.has(profileId)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Await startup recovery before removing a profile

When Jasmine starts while a remote prompt is active and the SSH recovery probe is still pending, the user can remove the profile before this map is populated. Recovery can then reserve and monitor the now-deleted profile after its local workspaces and transcripts have been erased, while the running remote operation loses its only usable profile entry. Await startupRecovery before this check, as the start, prompt, and stop paths already do.

Useful? React with 👍 / 👎.

Comment on lines +828 to +830
if (!isRetryableError(error)) throw error;
await new Promise((resolve) => setTimeout(resolve, DETACHED_OPERATION_POLL_MS));
continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep detached stops reserved across transient SSH failures

When Stop is requested for a detached operation during a transient SSH outage, runtime.stop() reports remote-stop-failed without setting retryable, so this branch throws immediately and the outer finally releases the reservation and retained client-proxy egress even though the remote task may still be running. Fresh evidence beyond the earlier attached-port abort comment is that this failure occurs in the detached monitor's daemon-stop command; classify transport stop failures as retryable or otherwise retain the operation, and cover this detached Stop variant.

AGENTS.md reference: AGENTS.md:L20-L20

Useful? React with 👍 / 👎.

Comment on lines +247 to +249
if ("pending" in result) {
onToastRef.current("Remote prompt accepted; waiting to synchronize the session");
return result;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reconcile pending submissions after they settle

When a first prompt returns pending because it detached or because post-settlement synchronization failed, this path only shows a toast and returns; the status listener updates profile status but never reloads sessions or workspaces. Consequently, even after the main-process monitor synchronizes the durable session, the new session remains absent from the current workspace until the user manually refreshes, despite the toast saying Jasmine is waiting to synchronize it. Arrange a completion-driven refresh and cover both new-session and existing-session pending variants.

AGENTS.md reference: AGENTS.md:L20-L20

Useful? React with 👍 / 👎.

@ArtificialNotImbecile

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 99af83a21a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +184 to +185
const stopped = await props.onAbortSession(starting ? undefined : sendingSessionId ?? sessionOperation?.sessionId ?? undefined);
if (!stopped) setStopping(false);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Clear stale stopping state after a successful stop

When a detached or startup-recovered operation is stopped successfully, this leaves stopping set to true; clearing sessionOperation only hides the button and does not reset the component state. If the user then starts another prompt in the same workspace, the newly displayed Stop button inherits the stale loading/disabled state and cannot cancel that prompt. Reset this flag when the tracked operation disappears or before beginning the next submission.

AGENTS.md reference: AGENTS.md:L11-L11

Useful? React with 👍 / 👎.

Comment thread src/main/services/remoteProfiles.ts Outdated
Comment on lines +798 to +800
if (error instanceof PiRemoteError && error.code === "runtime-not-installed") return;
if (!isRetryableError(error) || attempt === STARTUP_RECOVERY_ATTEMPTS - 1) return;
await new Promise((resolve) => setTimeout(resolve, 250 * 2 ** attempt));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep retrying startup recovery after transient outages

If Jasmine starts while the remote host is briefly unavailable, three retryable probe failures permanently end startup recovery; there is no later background retry unless the user happens to refresh that profile. For an already-running client-proxy prompt, the previous app process has closed its egress tunnel, so giving up here can leave the remote turn without provider access while the UI also has no tracked operation. Fresh evidence beyond the earlier restart finding is that recovery now runs at startup but stops permanently after this bounded loop; retain a background retry or otherwise keep the operation discoverable until the host answers.

AGENTS.md reference: AGENTS.md:L20-L20

Useful? React with 👍 / 👎.

@ArtificialNotImbecile

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 957c04038b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/main/services/remoteProfiles.ts Outdated
// Only client-proxy work depends on this app staying online after
// the bounded startup gate. Direct profiles remain daemon-owned and
// are rediscovered by their next explicit session refresh.
if (profile.network.mode === "client-proxy") void this.retryStartupRecovery(profile);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep background recovery in the per-profile gate

When the initial probes for a client-proxy profile all fail, this fire-and-forget retry is no longer tracked by startupRecoveryByProfile, so startSession and promptSession stop waiting for recovery while it is still unresolved. If the host returns and the user sends first, the new request reserves the empty local slot and rpc.start can reclaim the matching daemon RPC even while it is busy, allowing another prompt to be submitted before the old one is recovered. Fresh evidence beyond the earlier bounded-retry report is that the replacement retry now runs outside the gate; keep it associated with the profile until it either reserves the recovered operation or proves none exists, and cover the reconnect-versus-Send race.

AGENTS.md reference: AGENTS.md:L20-L20

Useful? React with 👍 / 👎.

@ArtificialNotImbecile

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 46d647eb5e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/main/services/remoteProfiles.ts Outdated
await delayUnref(delayMs);
if (this.cancelledStartupRecovery.has(profile.id)) return;
try {
const info = await this.runtime.inspectRuntime(profile, { install: false });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Refresh startup recovery after profile edits

When an offline client-proxy profile is edited while persistent startup recovery is running, this loop continues probing the captured pre-edit RemoteProfile. Because startSession and promptSession await that same recovery promise, correcting the SSH host, port, or network mode has no effect and the composer can remain in Starting indefinitely against the old configuration; reload the profile during retries or cancel and restart its recovery when the profile is updated, with a regression for the edit-during-recovery path.

AGENTS.md reference: AGENTS.md:L20-L20

Useful? React with 👍 / 👎.

Comment thread src/main/services/remoteProfiles.ts Outdated
{ phase: "session", remediation: "Open the remote session history to verify what was saved before retrying." }
);
}
if (!active?.busy) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Report a vanished RPC instead of treating it as settled

When the Pi RPC child crashes or is killed while a prompt is detached but the daemon itself remains alive, info.daemonId still matches while activeRpc becomes null; this branch therefore refreshes and clears the operation as though an idle RPC had confirmed successful settlement. Unlike the earlier daemon-restart case, the daemon epoch has not changed, so the new check does not catch this failure and the UI can report synchronization for incomplete work; treat a missing same-daemon RPC as an unconfirmed failure and cover that process-exit variant.

AGENTS.md reference: AGENTS.md:L20-L20

Useful? React with 👍 / 👎.

@ArtificialNotImbecile

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 686a69d817

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/main/services/remoteProfiles.ts Outdated
* connection, which leaves remote work running.
*/
async stopProfile(profileId: string): Promise<RemoteProfileStatus> {
await this.awaitProfileStartupRecovery(profileId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Let Stop cancel persistent startup recovery

When Jasmine starts with an offline client-proxy profile, recoverProfileOnStartup enters the unbounded retryStartupRecovery loop, so this await never completes and Stop remote runtime never reaches runtime.stop() or returns a failure. The fresh evidence beyond the earlier bounded/global recovery reports is that persistent recovery is now intentionally infinite but this same-profile Stop path has no cancellation mechanism, leaving the visible control loading until the host reconnects; explicit Stop should cancel or bypass recovery before attempting its own bounded stop.

AGENTS.md reference: AGENTS.md:L11-L11

Useful? React with 👍 / 👎.

@ArtificialNotImbecile

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dbe5b8aa3f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// Explicit Stop supersedes startup recovery. Do not wait for an unbounded
// offline client-proxy gate; any in-flight probe checks this cancellation
// before it can reserve the recovered operation.
this.cancelledStartupRecovery.add(profileId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Resume recovery when profile stop fails

When Stop remote runtime is attempted during a transient SSH outage, this permanently cancels the profile's startup-recovery loop before runtime.stop() reports failure. For a startup-recovered client-proxy prompt, the remote process may still be running but no background probe will reconnect its required egress when the host returns, leaving the work untracked until an incidental manual session refresh; clear the cancellation or restart recovery when the stop fails, and cover this failed-Stop variant.

AGENTS.md reference: AGENTS.md:L20-L20

Useful? React with 👍 / 👎.

@ArtificialNotImbecile

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f98998d880

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/main/services/remoteProfiles.ts Outdated

/** Creates the session and runs its first prompt on one RPC port, then publishes the durable result. */
async startSession(profileId: string, cwd: string, text: string): Promise<RemoteSessionStartResult | RemoteSessionSubmissionPending> {
await this.awaitProfileStartupRecovery(profileId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Make composer Stop cancel startup recovery

When Send is issued while startup recovery is pending—especially for an offline client-proxy profile whose retry loop is unbounded—this await occurs before reserveOperation. RemoteSessionPage immediately renders Stop from its local starting/sending state, but abortSession finds no entry in activeOperations and returns false, leaving the IPC request and composer stuck until SSH recovers. Reserve a cancellable operation before waiting, or let the composer abort cancel the per-profile recovery.

AGENTS.md reference: AGENTS.md:L11-L11

Useful? React with 👍 / 👎.

@ArtificialNotImbecile

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a5ed336657

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/main/services/remoteProfiles.ts Outdated
// Only client-proxy work depends on this app staying online after
// the bounded startup gate. Direct profiles remain daemon-owned and
// are rediscovered by their next explicit session refresh.
if (currentProfile.network.mode === "client-proxy") await this.retryStartupRecovery(profile.id);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep remote-direct recovery gated after startup outages

For a remote-direct profile that still has a busy prompt when Jasmine restarts offline, this branch abandons recovery after three probes and removes the Send gate. If the host later returns and the user prompts the same session before an explicit refresh, daemon.ts lines 224–226 reclaims the matching RPC before checking busy, and sendRpc writes the second prompt into the already-running process. Continue discovery for direct profiles as well, or reject turn-starting commands when a reclaimed RPC is busy, and cover this egress-mode variant.

AGENTS.md reference: AGENTS.md:L20-L20

Useful? React with 👍 / 👎.

@ArtificialNotImbecile

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3b3b5105d3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/main/services/remoteSessionRun.ts Outdated
Comment on lines +35 to +36
await callbacks.onPromptDispatched?.();
await port.prompt(text, [], callbacks.onPromptAccepted);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Record dispatch only after the prompt enters the transport

When the control connection disconnects after openSession but before port.prompt() invokes DaemonClient.request, that request rejects before calling writable.write, yet this callback has already marked the prompt as dispatched. RemoteProfileService consequently returns pending, causing the renderer to discard the draft, while detached monitoring sees the idle RPC as settled even though the prompt was never sent. Move the dispatch boundary into the transport write path and cover this pre-write disconnect for both new and existing sessions.

AGENTS.md reference: AGENTS.md:L20-L20

Useful? React with 👍 / 👎.

Comment thread src/main/services/remoteProfiles.ts Outdated
Comment on lines +173 to +174
} finally {
if (!removed) this.cancelledStartupRecovery.delete(profileId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Resume startup recovery when profile removal fails

When removal cancels persistent startup recovery for an offline profile but ProfileStore.remove() then fails because of a write or lock error, the profile remains configured while its recovery promise has already exited. Clearing only the cancellation flag here does not schedule another probe, so an undiscovered remote operation—especially a client-proxy turn needing its egress restored—remains untracked and a later submission can race it. Restart recovery on every non-removed exit and cover the failed-removal variant.

AGENTS.md reference: AGENTS.md:L20-L20

Useful? React with 👍 / 👎.

@ArtificialNotImbecile

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@ArtificialNotImbecile
ArtificialNotImbecile marked this pull request as ready for review August 21, 2026 14:36
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@ArtificialNotImbecile
ArtificialNotImbecile merged commit a2a4466 into main Aug 21, 2026
6 checks passed
@ArtificialNotImbecile
ArtificialNotImbecile deleted the codex/remote-session-workflow branch August 21, 2026 14:36
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.

1 participant