From b4a59656025c6eb863ec4755b67a58820aab6d69 Mon Sep 17 00:00:00 2001 From: SiYue <2835601846@qq.com> Date: Sat, 25 Apr 2026 00:31:36 +0800 Subject: [PATCH] refactor(onboard,api): harden copydir repo-root detection and use platform-neutral proc attrs naming Address latest review comments from sky5454 in PR #2654. scripts/copydir.go: - Improve repository root detection in a safer, more deterministic way. - Prefer locating repo root from the script source path via runtime.Caller(), then fallback to upward search from current working directory. - Replace .git-only root detection with repository anchor validation: go.sum, LICENSE, and .github must exist. - Keep \ placeholder expansion and existing in-repo path guards. - Preserve destination safety check to prevent deleting/copying to repo root. web/backend/api: - Rename applyLauncherWindowsProcAttrs() to applyLauncherProcAttrs() to expose a platform-independent interface name. - Keep platform-specific behavior split by build tags: windows keeps HideWindow SysProcAttr setup, non-windows remains no-op. - Update gateway startup path to call the renamed helper. Why: - Follow reviewer feedback to avoid relying on .git detection alone and prefer runtime/file-anchor based repository location. - Improve naming clarity by making cross-platform interfaces generic while preserving OS-specific implementation details internally. Validation: - go test ./cmd/picoclaw/internal/onboard - go test ./web/backend/api --- scripts/copydir.go | 30 ++++++++++++++++++++++++++++-- web/backend/api/exec_nonwindows.go | 2 +- web/backend/api/exec_windows.go | 4 ++-- web/backend/api/gateway.go | 2 +- 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/scripts/copydir.go b/scripts/copydir.go index 35622ab17..6e2777612 100644 --- a/scripts/copydir.go +++ b/scripts/copydir.go @@ -5,6 +5,7 @@ import ( "io" "os" "path/filepath" + "runtime" "strings" ) @@ -57,6 +58,17 @@ func main() { } func findRepoRoot() (string, error) { + _, file, _, ok := runtime.Caller(0) + if !ok { + return "", fmt.Errorf("unable to locate copydir.go source path") + } + + scriptDir := filepath.Dir(file) + candidate := filepath.Clean(filepath.Join(scriptDir, "..")) + if err := validateRepoRoot(candidate); err == nil { + return candidate, nil + } + wd, err := os.Getwd() if err != nil { return "", err @@ -68,17 +80,31 @@ func findRepoRoot() (string, error) { } for { - if _, err := os.Stat(filepath.Join(cur, ".git")); err == nil { + if err := validateRepoRoot(cur); err == nil { return filepath.Clean(cur), nil } parent := filepath.Dir(cur) if parent == cur { - return "", fmt.Errorf("could not find .git from %s", wd) + return "", fmt.Errorf("could not find repository root from %s", wd) } cur = parent } } +func validateRepoRoot(root string) error { + anchors := []string{ + filepath.Join(root, "go.sum"), + filepath.Join(root, "LICENSE"), + filepath.Join(root, ".github"), + } + for _, anchor := range anchors { + if _, err := os.Stat(anchor); err != nil { + return fmt.Errorf("missing repo anchor %s: %w", anchor, err) + } + } + return nil +} + func normalizePathArg(arg, repoRoot string) (string, error) { resolved := strings.ReplaceAll(arg, "${codespace}", repoRoot) abs, err := filepath.Abs(resolved) diff --git a/web/backend/api/exec_nonwindows.go b/web/backend/api/exec_nonwindows.go index a68a3bfd7..0dc3c0e94 100644 --- a/web/backend/api/exec_nonwindows.go +++ b/web/backend/api/exec_nonwindows.go @@ -8,4 +8,4 @@ func launcherExecCommand(name string, args ...string) *exec.Cmd { return exec.Command(name, args...) } -func applyLauncherWindowsProcAttrs(_ *exec.Cmd) {} +func applyLauncherProcAttrs(_ *exec.Cmd) {} diff --git a/web/backend/api/exec_windows.go b/web/backend/api/exec_windows.go index 1e76f8c73..86d3193a0 100644 --- a/web/backend/api/exec_windows.go +++ b/web/backend/api/exec_windows.go @@ -9,11 +9,11 @@ import ( func launcherExecCommand(name string, args ...string) *exec.Cmd { cmd := exec.Command(name, args...) - applyLauncherWindowsProcAttrs(cmd) + applyLauncherProcAttrs(cmd) return cmd } -func applyLauncherWindowsProcAttrs(cmd *exec.Cmd) { +func applyLauncherProcAttrs(cmd *exec.Cmd) { if cmd == nil { return } diff --git a/web/backend/api/gateway.go b/web/backend/api/gateway.go index d3e5ae1d5..606c8351d 100644 --- a/web/backend/api/gateway.go +++ b/web/backend/api/gateway.go @@ -706,7 +706,7 @@ func (h *Handler) startGatewayLocked(initialStatus string, existingPid int) (int logger.InfoC("gateway", fmt.Sprintf("Starting gateway process (%s)", execPath)) cmd = gatewayExecCommand(execPath, h.gatewayCommandArgs()...) - applyLauncherWindowsProcAttrs(cmd) + applyLauncherProcAttrs(cmd) cmd.Env = os.Environ() // Forward the launcher's config path via the environment variable that // GetConfigPath() already reads, so the gateway sub-process uses the same