From 374e5cccf203aa8a0247a3c8c6d0119c62321612 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9B=BE=E6=96=87=E9=94=8B0668000834?= Date: Tue, 7 Apr 2026 14:31:33 +0800 Subject: [PATCH 1/2] docs: document Issue #2373 gateway stop command requirement --- README_Issue_2373.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 README_Issue_2373.md diff --git a/README_Issue_2373.md b/README_Issue_2373.md new file mode 100644 index 000000000..df8582095 --- /dev/null +++ b/README_Issue_2373.md @@ -0,0 +1,13 @@ +# Issue #2373: Gateway不支持stop等命令 + +## Problem +gateway命令不支持stop子命令,用户只能通过killall来停止,但这会导致进程自动重启。 + +## Analysis +The gateway command implementation may be missing the stop subcommand handler. + +## Recommended Fix +1. Add 'stop' subcommand to gateway command in cmd/picoclaw/internal/gateway/command.go +2. Implement graceful shutdown logic +3. Send signal to running gateway process to stop it +4. Handle PID file management if needed From 395a55d3d941c491c0ad676850f38c4f951c2169 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9B=BE=E6=96=87=E9=94=8B0668000834?= Date: Tue, 7 Apr 2026 14:49:02 +0800 Subject: [PATCH 2/2] feat(gateway): add stop subcommand to gracefully shutdown gateway Add `picoclaw gateway stop` command that: - Reads the PID file from picoclaw home directory - Validates the gateway process is running - Sends SIGTERM (Unix) or Kill (Windows) to stop it Also update tests to reflect new subcommand. Fixes #2373 --- cmd/picoclaw/internal/gateway/command.go | 39 +++++++++++++++++++ cmd/picoclaw/internal/gateway/command_test.go | 3 +- cmd/picoclaw/internal/gateway/stop_unix.go | 13 +++++++ cmd/picoclaw/internal/gateway/stop_windows.go | 12 ++++++ 4 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 cmd/picoclaw/internal/gateway/stop_unix.go create mode 100644 cmd/picoclaw/internal/gateway/stop_windows.go diff --git a/cmd/picoclaw/internal/gateway/command.go b/cmd/picoclaw/internal/gateway/command.go index 7fa588c5c..f5bd91747 100644 --- a/cmd/picoclaw/internal/gateway/command.go +++ b/cmd/picoclaw/internal/gateway/command.go @@ -2,12 +2,14 @@ package gateway import ( "fmt" + "os" "github.com/spf13/cobra" "github.com/sipeed/picoclaw/cmd/picoclaw/internal" "github.com/sipeed/picoclaw/pkg/gateway" "github.com/sipeed/picoclaw/pkg/logger" + "github.com/sipeed/picoclaw/pkg/pid" "github.com/sipeed/picoclaw/pkg/utils" ) @@ -48,5 +50,42 @@ func NewGatewayCommand() *cobra.Command { "Continue starting even when no default model is configured", ) + // Add stop subcommand + cmd.AddCommand(newGatewayStopCommand()) + return cmd } + +func newGatewayStopCommand() *cobra.Command { + return &cobra.Command{ + Use: "stop", + Short: "Stop the running picoclaw gateway", + Long: `Stop the running picoclaw gateway by reading its PID file and sending a termination signal.`, + Args: cobra.NoArgs, + RunE: func(_ *cobra.Command, _ []string) error { + homePath := internal.GetPicoclawHome() + + // Read PID file and check if process is running + data := pid.ReadPidFileWithCheck(homePath) + if data == nil { + fmt.Println("gateway is not running") + return nil + } + + // Find the process + process, err := os.FindProcess(data.PID) + if err != nil { + return fmt.Errorf("failed to find gateway process (PID: %d): %w", data.PID, err) + } + + // Send termination signal + fmt.Printf("stopping gateway (PID: %d)...\n", data.PID) + if err := stopProcess(process); err != nil { + return fmt.Errorf("failed to stop gateway (PID: %d): %w", data.PID, err) + } + + fmt.Println("gateway stopped successfully") + return nil + }, + } +} diff --git a/cmd/picoclaw/internal/gateway/command_test.go b/cmd/picoclaw/internal/gateway/command_test.go index 839a7315a..91f3cc92b 100644 --- a/cmd/picoclaw/internal/gateway/command_test.go +++ b/cmd/picoclaw/internal/gateway/command_test.go @@ -24,7 +24,8 @@ func TestNewGatewayCommand(t *testing.T) { assert.Nil(t, cmd.PersistentPreRun) assert.Nil(t, cmd.PersistentPostRun) - assert.False(t, cmd.HasSubCommands()) + assert.True(t, cmd.HasSubCommands()) + assert.NotNil(t, cmd.Commands()) assert.True(t, cmd.HasFlags()) assert.NotNil(t, cmd.Flags().Lookup("debug")) diff --git a/cmd/picoclaw/internal/gateway/stop_unix.go b/cmd/picoclaw/internal/gateway/stop_unix.go new file mode 100644 index 000000000..e4a5052f9 --- /dev/null +++ b/cmd/picoclaw/internal/gateway/stop_unix.go @@ -0,0 +1,13 @@ +//go:build !windows + +package gateway + +import ( + "os" + "syscall" +) + +// stopProcess sends SIGTERM to the process for graceful shutdown on Unix-like systems. +func stopProcess(process *os.Process) error { + return process.Signal(syscall.SIGTERM) +} diff --git a/cmd/picoclaw/internal/gateway/stop_windows.go b/cmd/picoclaw/internal/gateway/stop_windows.go new file mode 100644 index 000000000..99b3065d1 --- /dev/null +++ b/cmd/picoclaw/internal/gateway/stop_windows.go @@ -0,0 +1,12 @@ +//go:build windows + +package gateway + +import ( + "os" +) + +// stopProcess kills the process on Windows (SIGTERM is not supported). +func stopProcess(process *os.Process) error { + return process.Kill() +}