ScuttleBot
fix: restart mirror and input loops after SIGUSR1 reconnection The SIGUSR1 handler now restarts mirrorSessionLoop and relayInputLoop with the new connector after reconnecting. Previously only the IRC connection was swapped but the mirror/input goroutines held stale references to the old connector, causing "mirror failed: context deadline exceeded" errors.
Commit
c0cc5deead6d9c39a09d90fd0f949f1eab85fcb2ae630aef4018be1cea7d33ad
Parent
fde91c692121611…
1 file changed
+8
-3
+8
-3
| --- cmd/claude-relay/main.go | ||
| +++ cmd/claude-relay/main.go | ||
| @@ -196,11 +196,10 @@ | ||
| 196 | 196 | "SCUTTLEBOT_ACTIVITY_VIA_BROKER="+boolString(relayActive), |
| 197 | 197 | ) |
| 198 | 198 | if relayActive { |
| 199 | 199 | go mirrorSessionLoop(ctx, relay, cfg, startedAt) |
| 200 | 200 | go presenceLoopPtr(ctx, &relay, cfg.HeartbeatInterval) |
| 201 | - go handleReconnectSignal(ctx, &relay, cfg) | |
| 202 | 201 | } |
| 203 | 202 | |
| 204 | 203 | if !isInteractiveTTY() { |
| 205 | 204 | cmd.Stdin = os.Stdin |
| 206 | 205 | cmd.Stdout = os.Stdout |
| @@ -251,10 +250,11 @@ | ||
| 251 | 250 | go func() { |
| 252 | 251 | copyPTYOutput(ptmx, os.Stdout, state) |
| 253 | 252 | }() |
| 254 | 253 | if relayActive { |
| 255 | 254 | go relayInputLoop(ctx, relay, cfg, state, ptmx, onlineAt) |
| 255 | + go handleReconnectSignal(ctx, &relay, cfg, state, ptmx, startedAt) | |
| 256 | 256 | } |
| 257 | 257 | |
| 258 | 258 | err = cmd.Wait() |
| 259 | 259 | cancel() |
| 260 | 260 | |
| @@ -639,11 +639,11 @@ | ||
| 639 | 639 | } |
| 640 | 640 | |
| 641 | 641 | // handleReconnectSignal listens for SIGUSR1 and tears down/rebuilds |
| 642 | 642 | // the IRC connection. The relay-watchdog sidecar sends this signal |
| 643 | 643 | // when it detects the server restarted or the network is down. |
| 644 | -func handleReconnectSignal(ctx context.Context, relayPtr *sessionrelay.Connector, cfg config) { | |
| 644 | +func handleReconnectSignal(ctx context.Context, relayPtr *sessionrelay.Connector, cfg config, state *relayState, ptmx *os.File, startedAt time.Time) { | |
| 645 | 645 | sigCh := make(chan os.Signal, 1) |
| 646 | 646 | signal.Notify(sigCh, syscall.SIGUSR1) |
| 647 | 647 | defer signal.Stop(sigCh) |
| 648 | 648 | |
| 649 | 649 | for { |
| @@ -694,15 +694,20 @@ | ||
| 694 | 694 | continue |
| 695 | 695 | } |
| 696 | 696 | cancel() |
| 697 | 697 | |
| 698 | 698 | *relayPtr = conn |
| 699 | + now := time.Now() | |
| 699 | 700 | _ = conn.Post(context.Background(), fmt.Sprintf( |
| 700 | 701 | "reconnected in %s; mention %s to interrupt", |
| 701 | 702 | filepath.Base(cfg.TargetCWD), cfg.Nick, |
| 702 | 703 | )) |
| 703 | - fmt.Fprintf(os.Stderr, "claude-relay: reconnected successfully\n") | |
| 704 | + fmt.Fprintf(os.Stderr, "claude-relay: reconnected, restarting mirror and input loops\n") | |
| 705 | + | |
| 706 | + // Restart mirror and input loops with the new connector. | |
| 707 | + go mirrorSessionLoop(ctx, conn, cfg, startedAt) | |
| 708 | + go relayInputLoop(ctx, conn, cfg, state, ptmx, now) | |
| 704 | 709 | break |
| 705 | 710 | } |
| 706 | 711 | } |
| 707 | 712 | } |
| 708 | 713 | |
| 709 | 714 |
| --- cmd/claude-relay/main.go | |
| +++ cmd/claude-relay/main.go | |
| @@ -196,11 +196,10 @@ | |
| 196 | "SCUTTLEBOT_ACTIVITY_VIA_BROKER="+boolString(relayActive), |
| 197 | ) |
| 198 | if relayActive { |
| 199 | go mirrorSessionLoop(ctx, relay, cfg, startedAt) |
| 200 | go presenceLoopPtr(ctx, &relay, cfg.HeartbeatInterval) |
| 201 | go handleReconnectSignal(ctx, &relay, cfg) |
| 202 | } |
| 203 | |
| 204 | if !isInteractiveTTY() { |
| 205 | cmd.Stdin = os.Stdin |
| 206 | cmd.Stdout = os.Stdout |
| @@ -251,10 +250,11 @@ | |
| 251 | go func() { |
| 252 | copyPTYOutput(ptmx, os.Stdout, state) |
| 253 | }() |
| 254 | if relayActive { |
| 255 | go relayInputLoop(ctx, relay, cfg, state, ptmx, onlineAt) |
| 256 | } |
| 257 | |
| 258 | err = cmd.Wait() |
| 259 | cancel() |
| 260 | |
| @@ -639,11 +639,11 @@ | |
| 639 | } |
| 640 | |
| 641 | // handleReconnectSignal listens for SIGUSR1 and tears down/rebuilds |
| 642 | // the IRC connection. The relay-watchdog sidecar sends this signal |
| 643 | // when it detects the server restarted or the network is down. |
| 644 | func handleReconnectSignal(ctx context.Context, relayPtr *sessionrelay.Connector, cfg config) { |
| 645 | sigCh := make(chan os.Signal, 1) |
| 646 | signal.Notify(sigCh, syscall.SIGUSR1) |
| 647 | defer signal.Stop(sigCh) |
| 648 | |
| 649 | for { |
| @@ -694,15 +694,20 @@ | |
| 694 | continue |
| 695 | } |
| 696 | cancel() |
| 697 | |
| 698 | *relayPtr = conn |
| 699 | _ = conn.Post(context.Background(), fmt.Sprintf( |
| 700 | "reconnected in %s; mention %s to interrupt", |
| 701 | filepath.Base(cfg.TargetCWD), cfg.Nick, |
| 702 | )) |
| 703 | fmt.Fprintf(os.Stderr, "claude-relay: reconnected successfully\n") |
| 704 | break |
| 705 | } |
| 706 | } |
| 707 | } |
| 708 | |
| 709 |
| --- cmd/claude-relay/main.go | |
| +++ cmd/claude-relay/main.go | |
| @@ -196,11 +196,10 @@ | |
| 196 | "SCUTTLEBOT_ACTIVITY_VIA_BROKER="+boolString(relayActive), |
| 197 | ) |
| 198 | if relayActive { |
| 199 | go mirrorSessionLoop(ctx, relay, cfg, startedAt) |
| 200 | go presenceLoopPtr(ctx, &relay, cfg.HeartbeatInterval) |
| 201 | } |
| 202 | |
| 203 | if !isInteractiveTTY() { |
| 204 | cmd.Stdin = os.Stdin |
| 205 | cmd.Stdout = os.Stdout |
| @@ -251,10 +250,11 @@ | |
| 250 | go func() { |
| 251 | copyPTYOutput(ptmx, os.Stdout, state) |
| 252 | }() |
| 253 | if relayActive { |
| 254 | go relayInputLoop(ctx, relay, cfg, state, ptmx, onlineAt) |
| 255 | go handleReconnectSignal(ctx, &relay, cfg, state, ptmx, startedAt) |
| 256 | } |
| 257 | |
| 258 | err = cmd.Wait() |
| 259 | cancel() |
| 260 | |
| @@ -639,11 +639,11 @@ | |
| 639 | } |
| 640 | |
| 641 | // handleReconnectSignal listens for SIGUSR1 and tears down/rebuilds |
| 642 | // the IRC connection. The relay-watchdog sidecar sends this signal |
| 643 | // when it detects the server restarted or the network is down. |
| 644 | func handleReconnectSignal(ctx context.Context, relayPtr *sessionrelay.Connector, cfg config, state *relayState, ptmx *os.File, startedAt time.Time) { |
| 645 | sigCh := make(chan os.Signal, 1) |
| 646 | signal.Notify(sigCh, syscall.SIGUSR1) |
| 647 | defer signal.Stop(sigCh) |
| 648 | |
| 649 | for { |
| @@ -694,15 +694,20 @@ | |
| 694 | continue |
| 695 | } |
| 696 | cancel() |
| 697 | |
| 698 | *relayPtr = conn |
| 699 | now := time.Now() |
| 700 | _ = conn.Post(context.Background(), fmt.Sprintf( |
| 701 | "reconnected in %s; mention %s to interrupt", |
| 702 | filepath.Base(cfg.TargetCWD), cfg.Nick, |
| 703 | )) |
| 704 | fmt.Fprintf(os.Stderr, "claude-relay: reconnected, restarting mirror and input loops\n") |
| 705 | |
| 706 | // Restart mirror and input loops with the new connector. |
| 707 | go mirrorSessionLoop(ctx, conn, cfg, startedAt) |
| 708 | go relayInputLoop(ctx, conn, cfg, state, ptmx, now) |
| 709 | break |
| 710 | } |
| 711 | } |
| 712 | } |
| 713 | |
| 714 |