fix(agent-help): back off failed trail probes · Entire

fix(agent-help): back off failed trail probes

39a28ab→main·

dipree·2d ago·3 files·+72 added/-0 removed

Sessions

01KXJYSD3YVB6X2ARB686DG09WView transcript

[?
Fix Agent Help Trail AvailabilityPi·GPT-5.6-sol·1 step](/content/gh/entireio/cli/session/019f65cc-0712-7af1-9340-2ded310ed0f4#timeline-01KXJYSD3YVB6X2ARB686DG09W/index.html)

Changes

3

88 unmodified lines

89
90
91
92
93
94
95
96
97
98
99
5 unmodified lines

105
106
107
108
109
110
111
112
113
114

88 unmodified lines

repoLine = scope.RepoKey
    }

now := time.Now()
    if decision := cachedTrailsEnablementForScope(ctx, scope, now); decision != trailEnablementCacheUnknown {
        return repoLine, decision == trailEnablementCacheEnabled
    }

// ResolveDataAPIToken performs data-host discovery before it can reject a
    // missing login. The scope already carries the locally resolved auth identity,
    // so avoid making an unauthenticated first run wait on a network request that
5 unmodified lines

refreshCtx, cancel := context.WithTimeout(ctx, trailEnablementRefreshTimeout)
    defer cancel()
    if err := refresh(refreshCtx, scope); err != nil {
        // A short negative cache keeps an offline authenticated user from paying
        // this timeout on every agent-help invocation while still retrying much
        // sooner than a definitive disabled result.
        _ = cacheTrailsEnablementRefreshFailure(ctx, scope, time.Now())
        return repoLine, false
    }
    return repoLine, cachedTrailsEnablementForScope(ctx, scope, time.Now()) == trailEnablementCacheEnabled

Mcmd/entire/cli/agent_help_cmd.go+9

3 unmodified lines

4
5
6
7
8
9
10
120 unmodified lines

131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187

3 unmodified lines

"bytes"
    "context"
    "encoding/json"
    "errors"
    "io"
    "os/exec"
    "strings"
120 unmodified lines

}
}

// A failed availability refresh is cached only long enough to prevent repeated
// blocking calls during a network outage, then becomes retryable.
// Not parallel: changes the process working directory and auth environment.
func TestAgentHelpRepoContext_CachesRefreshFailureBriefly(t *testing.T) {
    t.Setenv("ENTIRE_TOKEN", makeTestJWT(t, `{"iss":"https://auth.entire.io","sub":"user-1","handle":"alice","aud":"https://entire.io"}`))
    repoDir := t.TempDir()
    testutil.InitRepo(t, repoDir)
    cmd := exec.CommandContext(t.Context(), "git", "remote", "add", "origin", "git@github.com:acme/app.git")
    cmd.Dir = repoDir
    cmd.Env = testutil.GitIsolatedEnv()
    if err := cmd.Run(); err != nil {
        t.Fatalf("git remote add: %v", err)
    }
    t.Chdir(repoDir)

refreshCalls := 0
    _, enabled := agentHelpRepoContextWithRefresh(t.Context(), func(context.Context, trailEnablementScope) error {
        refreshCalls++
        return errors.New("offline")
    })
    if enabled {
        t.Fatal("trails should not be advertised after a failed availability refresh")
    }
    if refreshCalls != 1 {
        t.Fatalf("refresh calls after first invocation = %d, want 1", refreshCalls)
    }

// The failed attempt leaves a short-lived negative entry, so another
    // invocation does not repeat the blocking refresh.
    _, enabled = agentHelpRepoContextWithRefresh(t.Context(), func(context.Context, trailEnablementScope) error {
        refreshCalls++
        return errors.New("refresh should have been suppressed by the failure cache")
    })
    if enabled {
        t.Fatal("trails should remain unadvertised during the refresh-failure backoff")
    }
    if refreshCalls != 1 {
        t.Fatalf("refresh calls after second invocation = %d, want 1", refreshCalls)
    }

// Unlike a definitive disabled result, the failure entry becomes unknown
    // after the short backoff and can be retried.
    scope, err := currentTrailEnablementScope(t.Context())
    if err != nil {
        t.Fatalf("resolve trail scope: %v", err)
    }
    if got := cachedTrailsEnablementForScope(t.Context(), scope, time.Now().Add(trailEnablementRefreshFailureCacheTTL+time.Second)); got != trailEnablementCacheUnknown {
        t.Fatalf("cache after failure backoff = %v, want unknown", got)
    }
}

// Without a local auth identity, refreshing cannot produce a usable trails
// decision. Skip it locally so agent-help does not block on API discovery before
// auth eventually reports that the user is not logged in.