detached 517c29e

fix(remote): harden picker recovery routes

commit
517c29eb45d83454d77758da3f25448c6094335f
author
brice · · 2026-08-08 18:29 UTC
parent
f9840fe
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
5 files · +16 -5
M internal/ports/frame.go +1 -1
@@ -22,7 +22,7 @@ const MaxFrameLen = 16 << 20
2222 type MsgType uint8
2323
2424 // Frame message types. Client-originated messages occupy 1–13 and 15;
25-// server-originated messages occupy 16–23 and 25. Values 14 and 24 remain
25+// server-originated messages occupy 16–23 and 25–26. Values 14 and 24 remain
2626 // reserved for future extensions.
2727 const (
2828 MsgHello MsgType = 1
@@ -22,7 +22,7 @@ const MaxFrameLen = 16 << 20
22 type MsgType uint822 type MsgType uint8
23 23
24 // Frame message types. Client-originated messages occupy 1–13 and 15;24 // Frame message types. Client-originated messages occupy 1–13 and 15;
25-// server-originated messages occupy 16–23 and 25. Values 14 and 24 remain25+// server-originated messages occupy 16–23 and 25–26. Values 14 and 24 remain
26 // reserved for future extensions.26 // reserved for future extensions.
27 const (27 const (
28 MsgHello MsgType = 128 MsgHello MsgType = 1
M internal/usecase/client/client.go +8 -1
@@ -461,7 +461,11 @@ func (r *Runner) Run(ctx context.Context, request AttachRequest) (retErr error)
461461 if attemptRequest.NavigationCapabilities&ports.NavigationCapabilityHomePicker == 0 || homeRoute == nil {
462462 return errors.New("vev: stale home navigation action")
463463 }
464- returnRoute = &attachRoute{dialer: dialer, request: attemptRequest, resumeToken: result.resumeToken}
464+ returnRequest := attemptRequest
465+ if result.sessionName != "" {
466+ returnRequest.SessionName = result.sessionName
467+ }
468+ returnRoute = &attachRoute{dialer: dialer, request: returnRequest, resumeToken: result.resumeToken}
465469 homeNavigationPending = true
466470 dialer = homeRoute.dialer
467471 attemptRequest = homeRoute.request
@@ -543,6 +547,9 @@ func (r *Runner) Run(ctx context.Context, request AttachRequest) (retErr error)
543547 if result.sessionName != "" {
544548 attemptRequest.SessionName = result.sessionName
545549 }
550+ if result.welcomed {
551+ homeNavigationPending = false
552+ }
546553 if result.err == nil {
547554 if result.welcomed && returnNavigationPending {
548555 returnRoute = nil
@@ -461,7 +461,11 @@ func (r *Runner) Run(ctx context.Context, request AttachRequest) (retErr error)
461 if attemptRequest.NavigationCapabilities&ports.NavigationCapabilityHomePicker == 0 || homeRoute == nil {461 if attemptRequest.NavigationCapabilities&ports.NavigationCapabilityHomePicker == 0 || homeRoute == nil {
462 return errors.New("vev: stale home navigation action")462 return errors.New("vev: stale home navigation action")
463 }463 }
464- returnRoute = &attachRoute{dialer: dialer, request: attemptRequest, resumeToken: result.resumeToken}464+ returnRequest := attemptRequest
465+ if result.sessionName != "" {
466+ returnRequest.SessionName = result.sessionName
467+ }
468+ returnRoute = &attachRoute{dialer: dialer, request: returnRequest, resumeToken: result.resumeToken}
465 homeNavigationPending = true469 homeNavigationPending = true
466 dialer = homeRoute.dialer470 dialer = homeRoute.dialer
467 attemptRequest = homeRoute.request471 attemptRequest = homeRoute.request
@@ -543,6 +547,9 @@ func (r *Runner) Run(ctx context.Context, request AttachRequest) (retErr error)
543 if result.sessionName != "" {547 if result.sessionName != "" {
544 attemptRequest.SessionName = result.sessionName548 attemptRequest.SessionName = result.sessionName
545 }549 }
550+ if result.welcomed {
551+ homeNavigationPending = false
552+ }
546 if result.err == nil {553 if result.err == nil {
547 if result.welcomed && returnNavigationPending {554 if result.welcomed && returnNavigationPending {
548 returnRoute = nil555 returnRoute = nil
M internal/usecase/client/client_test.go +2 -1
@@ -61,10 +61,11 @@ func (c *reconnectTestClock) fireReconnect(t *testing.T) {
6161 c.mu.Lock()
6262 defer c.mu.Unlock()
6363 for _, timer := range c.timers {
64+ timer.mu.Lock()
6465 if timer.duration != 100*time.Millisecond {
66+ timer.mu.Unlock()
6567 continue
6668 }
67- timer.mu.Lock()
6869 if !timer.stopped && !timer.fired {
6970 timer.fired = true
7071 select {
@@ -61,10 +61,11 @@ func (c *reconnectTestClock) fireReconnect(t *testing.T) {
61 c.mu.Lock()61 c.mu.Lock()
62 defer c.mu.Unlock()62 defer c.mu.Unlock()
63 for _, timer := range c.timers {63 for _, timer := range c.timers {
64+ timer.mu.Lock()
64 if timer.duration != 100*time.Millisecond {65 if timer.duration != 100*time.Millisecond {
66+ timer.mu.Unlock()
65 continue67 continue
66 }68 }
67- timer.mu.Lock()
68 if !timer.stopped && !timer.fired {69 if !timer.stopped && !timer.fired {
69 timer.fired = true70 timer.fired = true
70 select {71 select {
M internal/usecase/daemon/picker.go +1 -1
@@ -944,7 +944,7 @@ func (d *Daemon) remotePickerTargetReady(key domain.RemoteSessionKey) bool {
944944 }
945945 for _, session := range entry.Sessions {
946946 if session.Name == key.Name {
947- return !remoteSessionStateStopped(session.State) && session.State != "broken"
947+ return session.State == "up"
948948 }
949949 }
950950 return false
@@ -944,7 +944,7 @@ func (d *Daemon) remotePickerTargetReady(key domain.RemoteSessionKey) bool {
944 }944 }
945 for _, session := range entry.Sessions {945 for _, session := range entry.Sessions {
946 if session.Name == key.Name {946 if session.Name == key.Name {
947- return !remoteSessionStateStopped(session.State) && session.State != "broken"947+ return session.State == "up"
948 }948 }
949 }949 }
950 return false950 return false
M internal/usecase/daemon/resume_test.go +4 -1
@@ -104,7 +104,10 @@ func TestTokenlessResumeIsRejectedBeforeNameRouting(t *testing.T) {
104104 var protocol *protoErr
105105 require.ErrorAs(t, err, &protocol)
106106 require.Equal(t, ports.ErrNoSuchSession, protocol.code)
107- require.Empty(t, d.sessions)
107+ d.mu.Lock()
108+ sessionCount := len(d.sessions)
109+ d.mu.Unlock()
110+ require.Zero(t, sessionCount)
108111 }
109112
110113 func TestResumeRejectsMismatchedRemoteTargetBeforeOwnershipMutation(t *testing.T) {
@@ -104,7 +104,10 @@ func TestTokenlessResumeIsRejectedBeforeNameRouting(t *testing.T) {
104 var protocol *protoErr104 var protocol *protoErr
105 require.ErrorAs(t, err, &protocol)105 require.ErrorAs(t, err, &protocol)
106 require.Equal(t, ports.ErrNoSuchSession, protocol.code)106 require.Equal(t, ports.ErrNoSuchSession, protocol.code)
107- require.Empty(t, d.sessions)107+ d.mu.Lock()
108+ sessionCount := len(d.sessions)
109+ d.mu.Unlock()
110+ require.Zero(t, sessionCount)
108 }111 }
109 112
110 func TestResumeRejectsMismatchedRemoteTargetBeforeOwnershipMutation(t *testing.T) {113 func TestResumeRejectsMismatchedRemoteTargetBeforeOwnershipMutation(t *testing.T) {

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 12

Tags 11

↑↓ moveenter openesc close