From 9cca5cc41babbd4d6491f5253e09edb8ae8da256 Mon Sep 17 00:00:00 2001 From: jordan Date: Sat, 7 Feb 2026 07:59:53 -0700 Subject: [PATCH] fix: add proper instrumentation to git clone for debugging - Log clone request with work_dir, URL, and token presence - Log workspace state (is_git_repo, existing remote) - Log all decision points (pull vs clone, clear workspace) - Detect and clear non-empty non-git directories before clone - Capture both stdout and stderr for clone failures - Include exit code in error messages --- internal/claudebox/git.go | 55 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/internal/claudebox/git.go b/internal/claudebox/git.go index 772625b..f101561 100644 --- a/internal/claudebox/git.go +++ b/internal/claudebox/git.go @@ -68,50 +68,97 @@ type CloneResult struct { func (g *GitOperations) CloneRepo(ctx context.Context, workDir, cloneURL string) *CloneResult { result := &CloneResult{} + g.logger.Info("git clone request", + "work_dir", workDir, + "clone_url", g.redactToken(cloneURL), + "has_token", g.giteaToken != "") + if cloneURL == "" { result.Error = fmt.Errorf("git clone URL is required") + g.logger.Error("git clone failed: empty URL") return result } // Check if already a git repo with the correct remote - if g.isGitRepo(ctx, workDir) { + isRepo := g.isGitRepo(ctx, workDir) + g.logger.Info("workspace check", "is_git_repo", isRepo, "work_dir", workDir) + + if isRepo { currentRemote, err := g.runGitOutput(ctx, workDir, "config", "--get", "remote.origin.url") currentRemote = strings.TrimSpace(currentRemote) + g.logger.Info("existing repo detected", + "current_remote", g.redactToken(currentRemote), + "expected_remote", g.redactToken(cloneURL), + "remote_match", currentRemote == cloneURL, + "remote_err", err) if err == nil && currentRemote == cloneURL { // Pull latest changes + g.logger.Info("pulling latest changes", "work_dir", workDir) if err := g.runGit(ctx, workDir, "pull", "--ff-only"); err != nil { // Pull failed but repo exists - continue with existing state - g.logger.Debug("git pull failed, continuing with existing state", "error", err, "work_dir", workDir) + g.logger.Warn("git pull failed, continuing with existing state", + "error", err, + "work_dir", workDir) } + g.logger.Info("git clone complete (existing repo)", "cloned", false) return result } // Different remote - clear and re-clone + g.logger.Info("clearing workspace for re-clone", "work_dir", workDir) if err := g.clearDir(ctx, workDir); err != nil { result.Error = fmt.Errorf("clear workspace: %w", err) + g.logger.Error("failed to clear workspace", "error", result.Error) return result } } + // Check if directory exists and is non-empty (would cause clone to fail) + if !isRepo { + checkCmd := exec.CommandContext(ctx, "sh", "-c", fmt.Sprintf("ls -A %s 2>/dev/null | head -1", workDir)) + output, _ := checkCmd.Output() + if len(strings.TrimSpace(string(output))) > 0 { + g.logger.Warn("workspace not empty but not a git repo, clearing", + "work_dir", workDir, + "first_file", strings.TrimSpace(string(output))) + if err := g.clearDir(ctx, workDir); err != nil { + result.Error = fmt.Errorf("clear non-empty workspace: %w", err) + g.logger.Error("failed to clear non-empty workspace", "error", result.Error) + return result + } + } + } + // Inject token for authentication authCloneURL := cloneURL if g.giteaToken != "" { authCloneURL = strings.Replace(cloneURL, "https://", "https://token:"+g.giteaToken+"@", 1) + } else { + g.logger.Warn("no gitea token configured, clone may fail for private repos") } // Clone the repository + g.logger.Info("executing git clone", "work_dir", workDir) cmd := exec.CommandContext(ctx, "git", "clone", authCloneURL, workDir) - var stderr bytes.Buffer + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout cmd.Stderr = &stderr if err := cmd.Run(); err != nil { errMsg := g.redactToken(stderr.String()) - result.Error = fmt.Errorf("git clone failed: %s: %s", err, errMsg) + stdoutMsg := g.redactToken(stdout.String()) + result.Error = fmt.Errorf("git clone exit %v: %s", err, errMsg) + g.logger.Error("git clone failed", + "error", err, + "stderr", errMsg, + "stdout", stdoutMsg, + "work_dir", workDir) return result } result.Cloned = true + g.logger.Info("git clone complete", "cloned", true, "work_dir", workDir) return result }