Skip to content

Fix remote Main loops closing on connect: the pane launches a sketch, not graphcoded (#253) - #256

Merged
scgopi merged 1 commit into
mainfrom
fix/253-main-loop-remote
Sep 3, 2026
Merged

scgopi merged 1 commit into
mainfrom
fix/253-main-loop-remote

Conversation

@scgopi

@scgopi scgopi commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Fixes #253.

What was wrong

A Main (sketch) loop opened on a remote project never reached its agent. The reporter's transcript shows the whole failure:

── Loop session is not running; waiting for graphcoded to start it. ──
shell closed: exit status 255
── Connection failed (exit 1). Retrying in 1s. Press Ctrl-C to stop. ──
── Remote session ended while disconnected. ──
Process exited. Press any key to close the terminal.

The remote connect dial in GhosttyTerminalView+Remote.remoteAgentScripts splits a missing session by owner: a turn-based loop restores itself, everything else waits for graphcoded. That split was written when the only attended type was turn-based (8a27eb1, 192e450, Aug 11–12). The sketch type landed three days later (5f7e85b) as a second attended type (runsUnattended is goal-or-time only, so the daemon deliberately never ensures a sketch) and never taught the remote dial about itself.

So a remote sketch:

  1. connect — no session, not turn-based → "waiting for graphcoded", exit 255. Nothing will ever start it.
  2. In a Codespace gh flattens every nonzero exit to 1, which the reconnect loop retries.
  3. reconnect — no session, and no boot marker (only a fresh launch writes one) → the "same boot" branch reads as the loop finished while disconnectedexit 0, pane closes.

Local sketches were unaffected: the local pane has no such gate.

The fix

  • LoopType.runsUnattended is now the single rule for who launches a loop; LoopNode.runsUnattended delegates to it.
  • Both remote dials gate the pane-owned restore on !loopType.runsUnattended, so a sketch gets the same resume-or-fresh launch a turn-based loop gets, on connect and behind a proven reboot.
  • Regression tests in RemoteLoopSurvivalTests for the Claude and Copilot (the reporter's backend) sketch dials.

Verification

  • xcodebuild test in a worktree: 1508 tests in 156 suites passed (1506 before, +2 added), both new tests green.
  • swiftlint 0 errors, swift format lint --strict clean on the changed files.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Pxwzz1AZnVE3peZ8qMejLa

…aunches a sketch (#253)

A Main (sketch) loop opened on a remote project — a Codespace in the report —
never reached its agent. The connect dial's missing-session branch keyed on
`loopType == .turnBased` and sent every other type to wait for graphcoded, but a
sketch is attended exactly like a turn-based loop: `runsUnattended` is goal-or-time
only, so the daemon deliberately never ensures one and nothing ever started it.
Over gh, where every nonzero exit flattens to 1, the retry loop redialed, found no
session and no boot marker, and closed the pane with "Remote session ended while
disconnected" — the transcript in the issue.

The ownership split (8a27eb1, 192e450) predates the sketch type (5f7e85b), which
never taught the remote dial about itself. Local sketches were unaffected.

- `LoopType.runsUnattended` is the one rule for who launches; `LoopNode`'s
  property delegates to it.
- Both remote dials gate the pane-owned restore on `!runsUnattended`, so a sketch
  gets the same resume-or-fresh launch a turn-based loop gets, on connect and
  behind a proven reboot.
- Regression tests for the Claude and Copilot sketch dials.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pxwzz1AZnVE3peZ8qMejLa
@scgopi
scgopi merged commit f144258 into main Sep 3, 2026
1 check passed
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.

Main loop not connecting properly

1 participant