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
This commit is contained in:
SiYue
2026-04-25 00:31:36 +08:00
parent 494cc381b5
commit b4a5965602
4 changed files with 32 additions and 6 deletions
+28 -2
View File
@@ -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)
+1 -1
View File
@@ -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) {}
+2 -2
View File
@@ -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
}
+1 -1
View File
@@ -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