Skip to content

Fix/repo sync river jobs - #148

Merged
Amazing-Stardom merged 13 commits into
masterfrom
fix/repo-sync-river-jobs
Nov 1, 2026
Merged

Amazing-Stardom merged 13 commits into
masterfrom
fix/repo-sync-river-jobs

Conversation

@Amazing-Stardom

@Amazing-Stardom Amazing-Stardom commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

🔍 LiveReview

⚙️ Customize risk-based approval policy for this repository or your organization.


Summary

  1. Fix river_job Bloat:

    • Immediately cancels (river.JobCancel) unauthenticated sync jobs on 401/403/missing token instead of retrying 5 times with large error traces.
    • Added a 6-hour error backoff in ReconciliationSweepWorker so failing repositories are not re-enqueued every 15 minutes.
    • Added job deduplication (ByArgs: true, 15m period) and reduced MaxAttempts from 5 to 3 for repo_pr_sync.
  2. Provider Compatibility & Error Classification:

    • Detects provider mismatches early (e.g. GitHub repo with GitLab connector) in webhooks and sync workers, failing fast with clear error messages.
    • Specific handling for HTTP 401 (Unauthorized) vs HTTP 403 (Forbidden).
  3. Frontend Connector Alerts & Dismissal:

    • Surfaces clear banner alerts for expired connector credentials and provider mismatches on /dashboard, /reviews, /explore/repositories, and /git.
    • Client-side dismissal persistence in localStorage so dismissed banners stay dismissed across page refreshes without needing DB migrations.
    • Fixed dismissal dedupe key matching (connector-${kind}-${connectorId}).
  4. Agent Guidelines:

    • Added Zero Code Loss guarantee and rebasing protocols in AGENTS.md.

Linked Issue

Fixes production river_job table bloat caused by infinite retry loops on unauthenticated repositories.

Validation

  • Backend tests passed:
    • go test -v ./internal/jobqueue -run "TestRepoPRSync|TestScheduledReview"
    • go test -v ./internal/api -run "TestWebhookOrchestratorV2_RejectsProviderMismatch"
  • Frontend tests passed:
    • npm test -- src/store/Notifications/__tests__/ (14/14 tests passed)
    • npm test -- src/components/reviews/diffviewer/reviewStreamState.test.ts (21/21 passed)
    • npm test -- src/lib/blastRadius.test.ts (9/9 passed)
  • TypeScript check: npx tsc --noEmit passed with 0 errors.

Checklist

  • This PR fulfills an agreed issue.
  • I kept the change narrow and scoped.
  • I ran the most specific relevant validation and described it above.
  • If I changed behavior, I called that out clearly in this PR.
  • If I touched UI, I attached a GIF or video walkthrough. This is required.
  • If this change touches security, disclosure flow, credentials, storage, network behavior, or licensing/entitlement enforcement, I reviewed SECURITY.md and updated related documentation if needed.
  • I have read and accept the Contributor License Agreement.

Notes For Reviewers

  • Zero database schema migrations or changes were introduced.
  • Notification dismissals are handled client-side via Redux + localStorage.
  • Rebased cleanly on top of master.

LiveReview auto-approval decisions

Review Time Decision Reason
#13600 1 Nov 2026, 08:13 UTC ⚠️ Force-approved Most of critical comments were critcal infromation about the changes. These risk i know it. So, I am approving it.
#13591 1 Nov 2026, 05:50 UTC ❌ Not approved Author level 1 < required 2
#13577 31 Oct 2026, 21:02 UTC ❌ Not approved Author level 1 < required 2
#13574 31 Oct 2026, 20:49 UTC ❌ Not approved Author level 1 < required 2
#13572 31 Oct 2026, 20:46 UTC ❌ Not approved Author level 1 < required 2
#13569 31 Oct 2026, 20:40 UTC ❌ Not approved Author level 1 < required 2
#13565 31 Oct 2026, 20:27 UTC ❌ Not approved Author level 1 < required 2
#13563 31 Oct 2026, 20:10 UTC ❌ Not approved Author level 1 < required 2
#13559 31 Oct 2026, 19:23 UTC ❌ Not approved Author level 1 < required 2
#13557 31 Oct 2026, 18:44 UTC ❌ Not approved Author level 1 < required 2

@livereview-agent

Copy link
Copy Markdown

Implement Repository Sync Error Handling and Webhook Validation

Overview

This change addresses river_job table bloat and prevents misconfigured webhook processing. It introduces structured authentication error handling across providers and workers. New UI components display aggregated connector-level alerts to users.

Technical Highlights

  • providers/shared.go: Defines AuthError and IsProviderCompatible for consistent error and type checking.
  • internal/jobqueue/repo_sync_worker.go: Cancels repository sync jobs for authentication errors, provider mismatches, or missing tokens.
  • internal/api/webhook_orchestrator_v2.go: Validates incoming webhook provider against the connector's configured provider.
  • ui/src/components/Connector/ConnectorAlerts.tsx: Implements a new UI system for displaying dismissible connector-related error alerts.
  • storage/providers/pullrequests/upsert.go: Adds provider compatibility validation during repository upsert operations.

Impact

  • Functionality: Users receive clear, actionable feedback on repository sync issues and webhooks process correctly.
  • Risk: New database lookups for repository upserts might affect performance; complex UI state management for alerts could introduce inconsistencies.


// Guard against webhook-to-connector provider mismatch: e.g. a GitHub webhook
// sent to a GitLab connector endpoint (/github-hook/:gitlab_connector_id).
if connProvider, ok := auth.GetConnectorProviderFromContext(c); ok && connProvider != "" {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

Please check the IsProviderCompatible function for any potential mismatches.

Suggestions:

  1. Ensure providers.IsProviderCompatible handles all expected provider types and compatibility rules correctly.

Queues: config.RiverQueueConfig(),
Workers: workers,
CompletedJobRetentionPeriod: 30 * 24 * time.Hour,
CancelledJobRetentionPeriod: 30 * 24 * time.Hour,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

Consider reducing the CancelledJobRetentionPeriod to 7 days.

Suggestions:

  1. Ensure 7 days is sufficient for debugging cancelled jobs before deletion. This change directly addresses the bloat issue.

return river.JobCancel(fmt.Errorf("fatal provider mismatch for repository %d: repo provider %q != connector %q", args.RepositoryID, info.Provider, info.ConnectorProvider))
}

if strings.TrimSpace(info.token()) == "" || info.token() == "NA" {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

The magic string 'NA' is used for a missing token. Please confirm its reliability.

Suggestions:

  1. Define 'NA' as a named constant if it's a specific sentinel value.
  2. Consider if an empty string or nil token should be sufficient for this check.

}
if err != nil {
var mismatchErr *pullrequests.ProviderMismatchError
if errors.As(err, &mismatchErr) || strings.Contains(err.Error(), "incompatible with connector") {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

The error string check is brittle. It would be preferable to check for a specific error type.

Suggestions:

  1. Ensure pullrequests.ProviderMismatchError is always returned or wrapped when this condition occurs.
  2. Avoid parsing error messages for control flow; rely on error types.

}, &river.InsertOpts{Queue: "repo_sync", MaxAttempts: 5})
}, &river.InsertOpts{
Queue: "repo_sync",
MaxAttempts: 3,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

The MaxAttempts has been reduced from 5 to 3. Please confirm if this is sufficient.

Suggestions:

  1. Verify if 3 attempts are sufficient for transient repo sync issues.
  2. Consider if specific error types should trigger different retry policies.

log.Printf("[ERROR] ScheduledReviewWorker: failed to load integration token %d for config %d: %v", cfg.ConnectorID, cfg.ID, err)
run.ErrorMessage = err.Error()
if err == sql.ErrNoRows {
_ = store.UpdateCheckpoint(ctx, cfg.ID, cfg.DefaultBranch.String, cfg.LastSyncedSHA.String, time.Now(), nextRunAt)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

The error returned by UpdateCheckpoint is being ignored. This could potentially mask checkpoint update failures.

Suggestions:

  1. Log errors from store.UpdateCheckpoint to ensure visibility if checkpoint updates fail.

run.ErrorMessage = err.Error()
if providers.IsAuthError(err) {
log.Printf("[WARN] ScheduledReviewWorker: auth error resolving GitHub app token for config %d: %v; cancelling job", cfg.ID, err)
_ = store.UpdateCheckpoint(ctx, cfg.ID, cfg.DefaultBranch.String, cfg.LastSyncedSHA.String, time.Now(), nextRunAt)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

The error returned by UpdateCheckpoint is being ignored. This could potentially mask checkpoint update failures.

Suggestions:

  1. Log errors from store.UpdateCheckpoint to ensure visibility if checkpoint updates fail.

if strings.TrimSpace(patToken) == "" {
run.ErrorMessage = "connector access token is missing"
log.Printf("[WARN] ScheduledReviewWorker: connector %d has empty access token; cancelling job", cfg.ConnectorID)
_ = store.UpdateCheckpoint(ctx, cfg.ID, cfg.DefaultBranch.String, cfg.LastSyncedSHA.String, time.Now(), nextRunAt)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

The error returned by UpdateCheckpoint is being ignored. This could potentially mask checkpoint update failures.

Suggestions:

  1. Log errors from store.UpdateCheckpoint to ensure visibility if checkpoint updates fail.

run.ErrorMessage = err.Error()
if providers.IsAuthError(err) {
log.Printf("[WARN] ScheduledReviewWorker: auth error resolving default branch for %s (config %d): %v; cancelling job", cfg.ProjectFullName, cfg.ID, err)
_ = store.UpdateCheckpoint(ctx, cfg.ID, cfg.DefaultBranch.String, cfg.LastSyncedSHA.String, time.Now(), nextRunAt)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

The error returned by UpdateCheckpoint is being ignored. This could potentially mask checkpoint update failures.

Suggestions:

  1. Log errors from store.UpdateCheckpoint to ensure visibility if checkpoint updates fail.

run.ErrorMessage = err.Error()
if providers.IsAuthError(err) {
log.Printf("[WARN] ScheduledReviewWorker: auth error resolving branch head for %s (config %d): %v; cancelling job", cfg.ProjectFullName, cfg.ID, err)
_ = store.UpdateCheckpoint(ctx, cfg.ID, branch, cfg.LastSyncedSHA.String, time.Now(), nextRunAt)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

The error returned by UpdateCheckpoint is being ignored. This could potentially mask checkpoint update failures.

Suggestions:

  1. Log errors from store.UpdateCheckpoint to ensure visibility if checkpoint updates fail.

run.ErrorMessage = err.Error()
if providers.IsAuthError(err) {
log.Printf("[WARN] ScheduledReviewWorker: auth error resolving lookback commit for %s (config %d): %v; cancelling job", cfg.ProjectFullName, cfg.ID, err)
_ = store.UpdateCheckpoint(ctx, cfg.ID, branch, headSHA, time.Now(), nextRunAt)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

The error returned by UpdateCheckpoint is being ignored. This could potentially mask checkpoint update failures.

Suggestions:

  1. Log errors from store.UpdateCheckpoint to ensure visibility if checkpoint updates fail.

run.ErrorMessage = err.Error()
if providers.IsAuthError(err) {
log.Printf("[WARN] ScheduledReviewWorker: auth error fetching compare diff for %s (config %d): %v; cancelling job", cfg.ProjectFullName, cfg.ID, err)
_ = store.UpdateCheckpoint(ctx, cfg.ID, branch, headSHA, time.Now(), nextRunAt)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

The error returned by UpdateCheckpoint is being ignored. This could potentially mask checkpoint update failures.

Suggestions:

  1. Log errors from store.UpdateCheckpoint to ensure visibility if checkpoint updates fail.

Comment thread internal/providers/shared.go Outdated
if strings.Contains(msg, "rate limit") || strings.Contains(msg, "too many requests") {
return false
}
return strings.Contains(msg, "403") && (

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

Generic string matching for 'status' or 'http' is too broad and can misclassify non-403 errors.

Suggestions:

  1. Rely primarily on AuthError.StatusCode for IsForbiddenError.
  2. Remove generic string checks like 'status' or 'http' if not an AuthError.

Comment thread internal/providers/shared.go Outdated
// - Bracketed status format (Azure DevOps, Gitea): "(401)"
has401 := strings.Contains(msg, "401") && (
strings.Contains(msg, "unauthorized") ||
strings.Contains(msg, "status") ||

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

Generic string matching for 'status' or 'http' is too broad and can misclassify non-401 errors.

Suggestions:

  1. Rely primarily on AuthError.StatusCode for IsAuthError.
  2. Remove generic string checks like 'status' or 'http' if not an AuthError.

Comment thread internal/providers/shared.go Outdated
// - "403 forbidden", "status 403", "http 403", "(403)"
has403 := strings.Contains(msg, "403") && (
strings.Contains(msg, "forbidden") ||
strings.Contains(msg, "status") ||

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

Generic string matching for 'status' or 'http' is too broad and can misclassify non-403 errors.

Suggestions:

  1. Rely primarily on AuthError.StatusCode for IsAuthError.
  2. Remove generic string checks like 'status' or 'http' if not an AuthError.

if r.ConnectorID > 0 {
var connProvider string
err := s.db.QueryRow(`SELECT provider FROM integration_tokens WHERE id = $1`, r.ConnectorID).Scan(&connProvider)
if err != nil && err != sql.ErrNoRows {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

When a database query error occurs on the connector ID, it is being handled with a general fmt.Errorf. It would be better to return a specific error or log the error instead.

Suggestions:

  1. Return a specific error type (e.g., ErrConnectorNotFound) if sql.ErrNoRows is encountered, or log the specific DB error.
  2. Consider if ConnectorID not found should be a ProviderMismatchError or a distinct error.

if err != nil && err != sql.ErrNoRows {
return 0, fmt.Errorf("failed to query connector %d for provider validation: %w", r.ConnectorID, err)
}
if err == nil && connProvider != "" && !providers.IsProviderCompatible(r.Provider, connProvider) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

If sql.ErrNoRows is returned for ConnectorID, connProvider becomes empty, and the compatibility check is skipped. Is this the intended behavior?

Suggestions:

  1. Clarify if a non-existent ConnectorID should bypass the provider compatibility check.
  2. Consider treating sql.ErrNoRows as an error indicating a missing connector, or explicitly handle it as a 'no provider to check against' scenario.

if r.ConnectorID > 0 {
var connProvider string
err := s.db.QueryRow(`SELECT provider FROM integration_tokens WHERE id = $1`, r.ConnectorID).Scan(&connProvider)
if err != nil && err != sql.ErrNoRows {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

When a database query error occurs on the connector ID, it is being handled with a general fmt.Errorf. It would be better to return a specific error or log the error instead.

Suggestions:

  1. Return a specific error type (e.g., ErrConnectorNotFound) if sql.ErrNoRows is encountered, or log the specific DB error.
  2. Consider if ConnectorID not found should be a ProviderMismatchError or a distinct error.

if err != nil && err != sql.ErrNoRows {
return 0, fmt.Errorf("failed to query connector %d for provider validation: %w", r.ConnectorID, err)
}
if err == nil && connProvider != "" && !providers.IsProviderCompatible(r.Provider, connProvider) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

If sql.ErrNoRows is returned for ConnectorID, connProvider becomes empty, and the compatibility check is skipped. Is this the intended behavior?

Suggestions:

  1. Clarify if a non-existent ConnectorID should bypass the provider compatibility check.
  2. Consider treating sql.ErrNoRows as an error indicating a missing connector, or explicitly handle it as a 'no provider to check against' scenario.

})
if err == nil {
t.Fatal("expected error when upserting github repo with gitlab connector, got nil")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

When asserting an error, please assert the specific error type ProviderMismatchError instead of just checking err != nil.

Suggestions:

  1. Use errors.As or errors.Is to assert that the returned error is specifically *ProviderMismatchError to ensure correct error propagation.

FullName: "acme/github-repo",
Name: "github-repo",
})
if err == nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

When asserting an error, please assert the specific error type ProviderMismatchError instead of just checking err != nil.

Suggestions:

  1. Use errors.As or errors.Is to assert that the returned error is specifically *ProviderMismatchError to ensure correct error propagation.

for (const repo of repos) {
if (repo.last_sync_status !== 'error' || !repo.last_sync_error) continue;
const err = repo.last_sync_error.toLowerCase();
const isForbidden =

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

A 403 (Forbidden) status code is often related to authentication. Please consider if isForbidden should classify as an auth kind.

Suggestions:

  1. Re-evaluate if 'forbidden' errors should be categorized as 'auth' for user action consistency.
  2. If distinct, ensure UI actions for 403 vs 401 are clear.

const [connectors, repoResp] = await Promise.all([
getConnectors(),
// Only pull error repos — targeted scan, not the full table.
getRepositories({ syncStatus: 'error', page: 1, perPage: 100 }),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

Only the first 100 error repositories are fetched, which may result in missed alerts if there are more errors.

Suggestions:

  1. Implement pagination to fetch all error repositories.
  2. Increase perPage limit if backend supports more than 100.

})
);
});
} catch {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

Alerts are silently skipped when a fetch error occurs. Consider logging these errors for better debuggability.

Suggestions:

  1. Add a console.error or logging mechanism to capture fetch failures.

});
};

const alerts = allAlerts.filter((a) => !dismissed.has(a.connector.id));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

The dismissAlert function updates the local state, but it does not update the Redux notification state for ConnectorAlerts.

Suggestions:

  1. Dispatch dismissNotification for ConnectorAlerts when dismissAlert is called, to keep Redux state consistent.
  2. Ensure ConnectorAlertBanner is rendered from Redux state if Redux is source of truth for all notifications.

useEffect(() => {
const progress = dashboardData?.connector_setup_progress || [];
progress.forEach((connector) => {
dispatch(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

A dedupeKey is missing for setup progress notifications. This could lead to duplicate notifications being added to Redux.

Suggestions:

  1. Add dedupeKey: connectorNotificationId(connector.connector_id) to addNotification for setup progress.

useEffect(() => {
const progress = dashboardData?.connector_setup_progress || [];
progress.forEach((connector) => {
dispatch(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

A title is missing for setup progress notifications, which is inconsistent with how error alerts are displayed.

Suggestions:

  1. Add a title property to setup progress notifications for consistency and clarity.
  2. Use getPhaseMessage to generate a suitable title.

useEffect(() => {
const progress = dashboardData?.connector_setup_progress || [];
progress.forEach((connector) => {
dispatch(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

The actions property is missing for setup progress notifications, which is inconsistent with how error alerts are displayed.

Suggestions:

  1. Add relevant actions (e.g., 'View Details', 'Retry Setup') to setup progress notifications for better UX.

connector.phase,
connector.connector_name,
connector.provider,
connector.total_projects,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: critical

The definitions for getPhaseVariant and getPhaseMessage are missing.

Suggestions:

  1. Define getPhaseVariant and getPhaseMessage functions within this component or import them from a utility file.

// URL: github.com uses api.github.com, while GitHub Enterprise instances serve
// their API under /api/v3 of the same host.
func apiBaseURL(baseURL string) string {
if baseURL == "" || baseURL == "https://github.com" {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

TrimSpace has been added for robustness against leading/trailing whitespace.

func apiBaseURL(baseURL string) string {
if baseURL == "" || baseURL == "https://github.com" {
u := strings.TrimRight(strings.TrimSpace(baseURL), "/")
if u == "" || u == "https://github.com" || u == "http://github.com" || u == "github.com" || u == "https://api.github.com" {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

The canonical URL https://api.github.com is now explicitly handled.

return "https://api.github.com"
}
return strings.TrimSuffix(baseURL, "/") + "/api/v3"
if strings.HasSuffix(u, "/api/v3") {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

The double appending of /api/v3 has been prevented, ensuring idempotency.

Suggestions:

  1. This check prevents ghe.example.com/api/v3/api/v3.

defer resp.Body.Close()
if resp.StatusCode != 200 {
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

Authentication error handling has been standardized with a specific AuthError type.

defer resp.Body.Close()
if resp.StatusCode != 200 {
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

Authentication error handling has been standardized with a specific AuthError type.

defer resp.Body.Close()
if resp.StatusCode != 200 {
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

Authentication error handling has been standardized with a specific AuthError type.

defer resp.Body.Close()
if resp.StatusCode != 200 {
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

Authentication error handling has been standardized with a specific AuthError type.

return nil, rlErr
}

if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

Authentication error handling has been standardized with a specific AuthError type.

if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("GitHub API request failed with status %d: %s", resp.StatusCode, string(body))
return nil, fmt.Errorf("GitHub API request failed with status %d: %s", resp.StatusCode, providers.FormatAPIResponseBody(body))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

FormatAPIResponseBody is now used for consistent error messages.

if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
return nil, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, providers.FormatAPIResponseBody(body))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

FormatAPIResponseBody is now used for consistent error messages.

func NewHTTPClient(baseURL, token string) *GitLabHTTPClient {
return &GitLabHTTPClient{
baseURL: fmt.Sprintf("%s/api/v4", baseURL),
baseURL: normalizeGitLabBaseURL(baseURL),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

normalizeGitLabBaseURL is now used for consistent URL generation.

defer resp.Body.Close()

// Check for errors
if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

Authentication error handling has been standardized with a specific AuthError type.

if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, "", fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
return nil, "", fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, providers.FormatAPIResponseBody(body))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

FormatAPIResponseBody is now used for consistent error messages.

"net/http"
"net/url"
"strconv"
"strings"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

The unused strings import has been removed.

params.Add("page", strconv.Itoa(page))
params.Add("per_page", "100")
requestURL := fmt.Sprintf("%s/api/v4/projects?%s", strings.TrimSuffix(baseURL, "/"), params.Encode())
requestURL := fmt.Sprintf("%s/projects?%s", normalizeGitLabBaseURL(baseURL), params.Encode())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

normalizeGitLabBaseURL is now used for consistent URL generation.

if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("GitLab API request failed with status %d: %s", resp.StatusCode, string(body))
return nil, fmt.Errorf("GitLab API request failed with status %d: %s", resp.StatusCode, providers.FormatAPIResponseBody(body))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

FormatAPIResponseBody is now used for consistent error messages.

return fmt.Sprintf("%s authentication failed: %s", e.Provider, e.Message)
}

func isHTTPStatusCode(msg, code, text string) bool {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

The regex for isHTTPStatusCode is more robust; the current string contains can have false positives (e.g., 'status 200 OK' containing 'status 20').

Suggestions:

  1. Use regular expressions for more precise matching of status codes, e.g., \bstatus\s*\d{3}\b.
  2. Consider parsing HTTP status code from error string if possible, rather than generic strings.Contains.

return false
}
}
msg := strings.ToLower(err.Error())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: info

Lowercase err.Error() once, and then use the msg variable.

Suggestions:

  1. Assign strings.ToLower(err.Error()) to msg once at the beginning of the function to avoid repeated conversions.

if strings.Contains(msg, "rate limit") || strings.Contains(msg, "too many requests") {
return false
}
return isHTTPStatusCode(msg, "403", "forbidden")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

String matching for 403 forbidden might have false positives; see the comment for isHTTPStatusCode.

Suggestions:

  1. Improve isHTTPStatusCode to use regex for more precise matching.

// IsAuthError returns true if err or any error in its chain is an *AuthError,
// or contains authentication error indicators for the specified provider (or across all
// known providers if no provider is passed).
func IsAuthError(err error, providerOpt ...string) bool {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

The providerOpt ...string allows multiple providers, but the function only uses the first. Clarify the intent or change the signature.

Suggestions:

  1. Change providerOpt ...string to provider string if only one provider is ever expected.
  2. If multiple providers are intended, iterate through providerOpt and apply logic for each.

}

// Common credential errors across all providers:
if strings.Contains(msg, "authentication failed") || strings.Contains(msg, "oauth token is invalid") {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

oauth token is invalid is very generic and could match non-authentication errors. Consider using more specific patterns.

Suggestions:

  1. Review logs/error messages for false positives.
  2. Refine pattern to be more specific, e.g., oauth: invalid token or oauth2: invalid token.


// Provider-specific checks:
switch {
case strings.HasPrefix(provider, "github"):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

Provider-specific checks should handle providerOpt being empty or nil for broader matching.

Suggestions:

  1. Ensure provider variable is correctly set to an empty string if providerOpt is empty, so strings.HasPrefix works as expected for the default case.

default:
// When no specific provider is passed, check across all known provider signatures:
authSignatures := []string{
"bad credentials",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

There is a duplicate invalid token in authSignatures (lines 234, 235). Remove one.

Suggestions:

  1. Remove the duplicate entry "invalid token" from authSignatures.

default:
// When no specific provider is passed, check across all known provider signatures:
authSignatures := []string{
"bad credentials",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

There is a duplicate token expired in authSignatures (lines 236, 238). Remove one.

Suggestions:

  1. Remove the duplicate entry "token expired" from authSignatures.

func IsProviderCompatible(repoProvider, connProvider string) bool {
repoNorm := strings.ToLower(repoProvider)
connNorm := strings.ToLower(connProvider)
if strings.HasPrefix(repoNorm, "github") && (strings.HasPrefix(connNorm, "github") || connNorm == "github_app") {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

connNorm == "github_app" should be strings.HasPrefix(connNorm, "github") to include github-enterprise.

Suggestions:

  1. Change connNorm == "github_app" to strings.HasPrefix(connNorm, "github") for consistency and to correctly match github-enterprise.

return ""
}
lower := strings.ToLower(s)
for _, tag := range []string{"<!doctype", "<html", "<head"} {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: warning

HTML tag detection could be more robust; for example, by checking for the Content-Type: text/html header first.

Suggestions:

  1. If possible, pass HTTP response headers to FormatAPIResponseBody and check Content-Type.
  2. Consider using a more sophisticated HTML detection library if simple string checks become insufficient.

@livereview-agent livereview-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ LiveReview policy: Not approved (blocked at Eligibility) · Review #13600

Stage Result Detail
Eligibility ❌ author level 1 < required 2
Static rules ⏭️ skipped (earlier stage blocked)
Risk score ⏭️ skipped (earlier stage blocked)
LLM classification ⏭️ skipped (earlier stage blocked)
Final decision ❌ eligibility: author level 1 < required 2

@livereview-agent livereview-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ LiveReview policy: Force-approved

Force-approved by lince@hexmos.com despite a policy block.

Reason: Most of critical comments were critcal infromation about the changes. These risk i know it. So, I am approving it.

This override is recorded in LiveReview's audit log.

@Amazing-Stardom
Amazing-Stardom merged commit 3b161aa into master Nov 1, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant