fix(agent-help): isolate trail refresh backoff · Entire
fix(agent-help): isolate trail refresh backoff
789278f→main·
dipree·2d ago·4 files·+59 added/-18 removed
Sessions
01KXJZ6CA411K8PPS44YE4J2NPView transcript
[?
Fix Agent Help Trail AvailabilityPi·GPT-5.6-sol·1 step](/content/gh/entireio/cli/session/019f65cc-0712-7af1-9340-2ded310ed0f4#timeline-01KXJZ6CA411K8PPS44YE4J2NP/index.html)
Changes
4
cmd/entire/cli
Magent_help_cmd.go+7/-4
Magent_help_cmd_test.go+10/-5
settings
Msettings.go+8
Mtrail_context_cache.go+34/-9
100 unmodified lines
101
102
103
104
105
106
107
108
109
110
108
109
110
111
111
112
113
114
115
116
117
100 unmodified lines
if scope.AuthKey == "" {
return repoLine, false
}
if recentAgentHelpTrailsRefreshFailure(ctx, scope, now) {
return repoLine, false
}
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())
// A separate short backoff keeps an offline authenticated user from paying
// this timeout on every agent-help invocation. It must not alter the shared
// enablement decision, which SessionStart uses for context injection.
_ = saveAgentHelpTrailsRefreshFailure(ctx, scope, time.Now())
return repoLine, false
}
return repoLine, cachedTrailsEnablementForScope(ctx, scope, time.Now()) == trailEnablementCacheEnabled
Mcmd/entire/cli/agent_help_cmd.go+7/-4
157 unmodified lines
158
159
160
161
161
162
163
164
6 unmodified lines
171
172
173
174
175
174
175
176
177
180
181
178
179
180
181
182
183
184
185
186
187
188
189
157 unmodified lines
t.Fatalf("refresh calls after first invocation = %d, want 1", refreshCalls)
}
// The failed attempt leaves a short-lived negative entry, so another
// The failed attempt leaves a short-lived agent-help-only backoff, so another
// invocation does not repeat the blocking refresh.
_, enabled = agentHelpRepoContextWithRefresh(t.Context(), func(context.Context, trailEnablementScope) error {
refreshCalls++
6 unmodified lines
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)
// The shared decision remains unknown, so SessionStart is not prevented from
// doing its own authoritative refresh and context-injection decision.
if got := cachedTrailsEnablementForScope(t.Context(), scope, time.Now()); got != trailEnablementCacheUnknown {
t.Fatalf("shared trails cache after agent-help failure = %v, want unknown", got)
}
// The agent-help-only marker expires after the short backoff and permits a
// later help invocation to retry.
if recentAgentHelpTrailsRefreshFailure(t.Context(), scope, time.Now().Add(agentHelpTrailsRefreshFailureBackoff+time.Second)) {
t.Fatal("agent-help refresh failure should expire after the backoff")
}
}
Mcmd/entire/cli/agent_help_cmd_test.go+10/-5
187 unmodified lines
188
189
190
191
192
193
194
195
196
197
198
199
200
201
187 unmodified lines
TrailsEnabledRepoKey string `json:"trails_enabled_repo_key,omitempty"`
TrailsEnabledAPIBase string `json:"trails_enabled_api_base,omitempty"`
TrailsEnabledAuthKey string `json:"trails_enabled_auth_key,omitempty"`
// Agent-help refresh failures use a separate, short-lived backoff. Keeping
// this out of TrailsEnabled ensures a transient help-command failure cannot
// suppress SessionStart's authoritative enablement probe or context injection.
TrailsAgentHelpRefreshFailedAt *time.Time `json:"trails_agent_help_refresh_failed_at,omitempty"`
TrailsAgentHelpFailureRepoKey string `json:"trails_agent_help_failure_repo_key,omitempty"`
TrailsAgentHelpFailureAPIBase string `json:"trails_agent_help_failure_api_base,omitempty"`
TrailsAgentHelpFailureAuthKey string `json:"trails_agent_help_failure_auth_key,omitempty"`
}
// SummaryGenerationSettings configures provider selection for on-demand
Mcmd/entire/cli/settings/settings.go+8
22 unmodified lines
23
24
25
26
26
27
28
29
166 unmodified lines
196
197
198
199
200
201
202
203
204
205
206
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
22 unmodified lines
const (
trailEnablementCacheTTL = time.Hour
trailEnablementRefreshFailureCacheTTL = 5 * time.Minute
agentHelpTrailsRefreshFailureBackoff = 5 * time.Minute
trailEnablementSessionStartRefreshTimeout = time.Second
trailEnablementRefreshTimeout = 3 * time.Second
)
166 unmodified lines
return nil
}
// cacheTrailsEnablementRefreshFailure records a short-lived disabled decision
// after an agent-help refresh fails. It prevents repeated invocations from each
// waiting on the same degraded network while retrying much sooner than a real
// server-provided disabled decision. Backdating reuses the existing cache schema
// and expiry logic while giving the entry only the failure TTL remaining.
func cacheTrailsEnablementRefreshFailure(ctx context.Context, scope trailEnablementScope, now time.Time) error {
checkedAt := now.Add(-trailEnablementCacheTTL + trailEnablementRefreshFailureCacheTTL)
return saveTrailsEnabledForScope(ctx, scope, false, checkedAt)
// recentAgentHelpTrailsRefreshFailure reports whether agent-help should back off
// after a failed availability refresh for this exact repo/API/auth scope. This
// marker is deliberately separate from TrailsEnabled: lifecycle SessionStart
// must still perform its authoritative probe and decide context injection.
func recentAgentHelpTrailsRefreshFailure(ctx context.Context, scope trailEnablementScope, now time.Time) bool {
prefs, err := settings.LoadClonePreferences(ctx)
if err != nil || prefs.TrailsAgentHelpRefreshFailedAt == nil {
return false
}
if prefs.TrailsAgentHelpFailureRepoKey != scope.RepoKey ||
prefs.TrailsAgentHelpFailureAPIBase != scope.APIBase ||
prefs.TrailsAgentHelpFailureAuthKey != scope.AuthKey {
return false
}
failedAt := *prefs.TrailsAgentHelpRefreshFailedAt
if failedAt.IsZero() || now.Before(failedAt) {
return false
}
return now.Sub(failedAt) <= agentHelpTrailsRefreshFailureBackoff
}
func saveAgentHelpTrailsRefreshFailure(ctx context.Context, scope trailEnablementScope, failedAt time.Time) error {
failedAtUTC := failedAt.UTC()
if err := settings.ModifyClonePreferences(ctx, func(prefs *settings.ClonePreferences) error {
prefs.TrailsAgentHelpRefreshFailedAt = &failedAtUTC
prefs.TrailsAgentHelpFailureRepoKey = scope.RepoKey
prefs.TrailsAgentHelpFailureAPIBase = scope.APIBase
prefs.TrailsAgentHelpFailureAuthKey = scope.AuthKey
return nil
}); err != nil {
return fmt.Errorf("save clone preferences: %w", err)
}
return nil
}
func refreshTrailsEnabledCacheIfStaleForScope(ctx context.Context, scope trailEnablementScope) error {