From ed95fc83821297a0e3dab448deb909f41fb38ea6 Mon Sep 17 00:00:00 2001 From: Lince Mathew Date: Thu, 23 Apr 2026 20:10:22 +0530 Subject: [PATCH 1/2] refactor: display manual re-authentication instructions LiveReview Pre-Commit Check: ran (iter:1, coverage:0%) --- internal/appcore/auth_recovery.go | 41 +++++++++++++++++++++++++++---- main.go | 9 +++++-- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/internal/appcore/auth_recovery.go b/internal/appcore/auth_recovery.go index 13cea6e..0a1b567 100644 --- a/internal/appcore/auth_recovery.go +++ b/internal/appcore/auth_recovery.go @@ -19,6 +19,11 @@ import ( "github.com/HexmosTech/git-lrc/storage" ) +// ErrAuthHandled is a sentinel error indicating that an authentication failure +// has already been handled visually (instructions printed) and should not be +// logged again by the top-level error handler. +var ErrAuthHandled = errors.New("authentication failure already handled") + const liveReviewAPIKeyInvalidCode = "LIVE_REVIEW_API_KEY_INVALID" type createAPIKeyRuntimeRequest struct { @@ -100,7 +105,7 @@ func submitReviewWithRecovery(config Config, base64Diff, repoName string, verbos recoveredConfig, recErr := recoverAPIKeyAndTokens(config, "submit") if recErr != nil { - return reviewmodel.DiffReviewCreateResponse{}, config, fmt.Errorf("auto-recovery failed after %s: %w", liveReviewAPIKeyInvalidCode, recErr) + return reviewmodel.DiffReviewCreateResponse{}, config, ErrAuthHandled } fmt.Println("Retrying review submission with refreshed credentials...") @@ -122,7 +127,7 @@ func pollReviewWithRecovery(config Config, reviewID string, pollInterval, timeou recoveredConfig, recErr := recoverAPIKeyAndTokens(config, "poll") if recErr != nil { - return nil, config, fmt.Errorf("auto-recovery failed after %s: %w", liveReviewAPIKeyInvalidCode, recErr) + return nil, config, ErrAuthHandled } fmt.Println("Retrying review polling with refreshed credentials...") @@ -133,6 +138,28 @@ func pollReviewWithRecovery(config Config, reviewID string, pollInterval, timeou return retryResult, recoveredConfig, nil } +func highlightCommand(cmd string) string { + return "\033[36m" + cmd + "\033[0m" +} + +func printManualReauthInstructions() { + const ( + cReset = "\033[0m" + cBold = "\033[1m" + cYellow = "\033[33m" + cDim = "\033[2m" + ) + + fmt.Println() + fmt.Printf(" %s%sšŸ” MANUAL RE-AUTHENTICATION REQUIRED%s\n", cBold, cYellow, cReset) + fmt.Printf(" %s─────────────────────────────────────────────────────%s\n", cDim, cReset) + fmt.Println() + fmt.Printf(" 1. Open the LRC UI by running: %s\n", highlightCommand("lrc ui")) + fmt.Printf(" 2. Click the %sRe-authenticate%s button\n", cBold, cReset) + fmt.Printf(" 3. Once authenticated, run the command again to continue\n") + fmt.Println() +} + func recoverAPIKeyAndTokens(config Config, phase string) (Config, error) { started := time.Now() diag := authRecoveryDiagnostic{ @@ -158,6 +185,7 @@ func recoverAPIKeyAndTokens(config Config, phase string) (Config, error) { diag.FailureReason = "missing org_id in config" reportDiagnosticWriteError(persistAuthRecoveryDiagnostic(&diag, time.Since(started))) fmt.Println("Automatic recovery unavailable: missing org_id in ~/.lrc.toml.") + printManualReauthInstructions() return config, fmt.Errorf("missing org_id in config") } @@ -186,6 +214,7 @@ func recoverAPIKeyAndTokens(config Config, phase string) (Config, error) { if createStatus != http.StatusUnauthorized || strings.TrimSpace(config.RefreshToken) == "" { diag.FailureReason = fmt.Sprintf("create API key failed before refresh: status=%d", createStatus) reportDiagnosticWriteError(persistAuthRecoveryDiagnostic(&diag, time.Since(started))) + printManualReauthInstructions() return config, fmt.Errorf("create API key failed: %w body=%s", err, strings.TrimSpace(createBody)) } @@ -195,6 +224,7 @@ func recoverAPIKeyAndTokens(config Config, phase string) (Config, error) { if refreshErr != nil { diag.FailureReason = fmt.Sprintf("refresh token failed: status=%d", refreshStatus) reportDiagnosticWriteError(persistAuthRecoveryDiagnostic(&diag, time.Since(started))) + printManualReauthInstructions() return config, fmt.Errorf("failed to refresh session: %w body=%s", refreshErr, strings.TrimSpace(refreshBody)) } @@ -216,11 +246,12 @@ func recoverAPIKeyAndTokens(config Config, phase string) (Config, error) { } fmt.Println("Session refreshed and tokens persisted to ~/.lrc.toml.") - newKey, createStatus, createBody, err = createAPIKeyWithJWT(updated.APIURL, updated.OrgID, updated.JWT) + newKey, createStatus, createBody, err = createAPIKeyWithJWT(config.APIURL, config.OrgID, newJWT) if err != nil { - diag.FailureReason = fmt.Sprintf("create API key after refresh failed: status=%d", createStatus) + diag.FailureReason = fmt.Sprintf("create API key failed after refresh: status=%d", createStatus) reportDiagnosticWriteError(persistAuthRecoveryDiagnostic(&diag, time.Since(started))) - return config, fmt.Errorf("failed to create API key after refresh: %w body=%s", err, strings.TrimSpace(createBody)) + printManualReauthInstructions() + return config, fmt.Errorf("failed to create API key after session refresh: %w body=%s", err, strings.TrimSpace(createBody)) } updated.APIKey = newKey diff --git a/main.go b/main.go index cfa433d..c9539a9 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,8 @@ package main import ( - "log" + "errors" + "fmt" "os" cmdapp "github.com/HexmosTech/git-lrc/cmd" @@ -72,7 +73,11 @@ func main() { }) if err := app.Run(os.Args); err != nil { - log.Fatal(err) + if errors.Is(err, appcore.ErrAuthHandled) { + os.Exit(1) + } + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) } } From a5338d20ed25c1810f888ab77d076264c1f49867 Mon Sep 17 00:00:00 2001 From: Lince Mathew Date: Thu, 23 Apr 2026 20:42:36 +0530 Subject: [PATCH 2/2] refactor LiveReview Pre-Commit Check: ran (iter:1, coverage:0%) --- internal/appcore/auth_recovery.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/internal/appcore/auth_recovery.go b/internal/appcore/auth_recovery.go index 0a1b567..8f9bead 100644 --- a/internal/appcore/auth_recovery.go +++ b/internal/appcore/auth_recovery.go @@ -19,9 +19,7 @@ import ( "github.com/HexmosTech/git-lrc/storage" ) -// ErrAuthHandled is a sentinel error indicating that an authentication failure -// has already been handled visually (instructions printed) and should not be -// logged again by the top-level error handler. +// ErrAuthHandled indicates an auth failure already handled visually to suppress redundant logs. var ErrAuthHandled = errors.New("authentication failure already handled") const liveReviewAPIKeyInvalidCode = "LIVE_REVIEW_API_KEY_INVALID" @@ -246,7 +244,7 @@ func recoverAPIKeyAndTokens(config Config, phase string) (Config, error) { } fmt.Println("Session refreshed and tokens persisted to ~/.lrc.toml.") - newKey, createStatus, createBody, err = createAPIKeyWithJWT(config.APIURL, config.OrgID, newJWT) + newKey, createStatus, createBody, err = createAPIKeyWithJWT(updated.APIURL, updated.OrgID, updated.JWT) if err != nil { diag.FailureReason = fmt.Sprintf("create API key failed after refresh: status=%d", createStatus) reportDiagnosticWriteError(persistAuthRecoveryDiagnostic(&diag, time.Since(started)))