Skip post-plugin version check after self-update plugin · Entire

Skip post-plugin version check after self-update plugin

5e12b0c→main·

toothbrush·5d ago·2 files·+75 added/-1 removed

After entire upgrade (the entire-upgrade plugin) replaces the binary on disk, the parent process still carries the pre-upgrade compiled-in version. The post-plugin version check then saw itself as outdated and prompted the user to redo the upgrade that had just completed, running a no-op brew upgrade.

Co-Authored-By: Claude Fable 5 noreply@anthropic.com

Sessions

01KXCCCE7M7G8D6HKP00QGPC4EView transcript

Changes

2

30 unmodified lines

31
32
33
34
35
36
37
38
39
40
41
42
43
44
12 unmodified lines

57
58
59
52
60
61
62
63
64
65
66
67
68
69

30 unmodified lines

agentPluginBinaryPrefix = "entire-agent-"
// selfUpdatePluginName is the plugin that replaces the entire binary on
disk (`entire upgrade` → entire-upgrade).
const selfUpdatePluginName = "upgrade"
// postPluginVersionCheck is a test seam for the version-check notice that
// fires after a successful plugin run.
var postPluginVersionCheck = versioncheck.CheckAndNotify

// MaybeRunPlugin returns (true, exitCode) when an external command was
// resolved and run. On launch failure (e.g. missing executable bit)
// returns (true, 1) after printing to stderr. On no-match returns
12 unmodified lines

maybeTrackPluginInvocation(ctx, pluginName)
 // Stderr, matching the built-in PersistentPostRun: the plugin's own
 // stdout may be machine-readable and piped.
 versioncheck.CheckAndNotify(ctx, os.Stderr, versioninfo.Version)
 //
 // Skipped after a self-update: this process still carries the
 // pre-upgrade compiled-in version, so the check would see itself as
 // outdated and prompt to redo the upgrade that just completed.
 if pluginName != selfUpdatePluginName {
 postPluginVersionCheck(ctx, os.Stderr, versioninfo.Version)
 }
}
return true, exitCode
}

Mcmd/entire/cli/plugin.go+15/-1

2 unmodified lines

3
4
5
6
7
8
9
182 unmodified lines

192
193
194
195
196
197
198
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
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256

2 unmodified lines

import (
 context
 "fmt"
 "io"
 "os"
 "path/filepath"
 "runtime"
182 unmodified lines

}

// interceptVersionCheck swaps the post-plugin version-check seam for a
// counter and restores it on cleanup.
func interceptVersionCheck(t *testing.T) *int {
t.Helper()
calls := 0
orig := postPluginVersionCheck
postPluginVersionCheck = func(context.Context, io.Writer, string) { calls++ }
t.Cleanup(func() { postPluginVersionCheck = orig })
return &calls
}

func TestMaybeRunPlugin_VersionCheckAfterSuccess(t *testing.T) { //nolint:paralleltest // mutates PATH and the version-check seam
dir := t.TempDir()
writePluginBinary(t, dir, "entire-pgr", filepath.Join(dir, "args.txt"), 0)
withPathDir(t, dir)
calls := interceptVersionCheck(t)

handled, code := MaybeRunPlugin(context.Background(), newTestRoot(), []string{"pgr"})
 if !handled || code != 0 {
t.Fatalf("handled=%v code=%d, want handled=true code=0", handled, code)
 }
 if *calls != 1 {
t.Errorf("version check calls: got %d, want 1", *calls)
 }
}

// After `entire upgrade` replaces the binary on disk, this process still
// carries the pre-upgrade compiled-in version — a post-run version check
// would see itself as outdated and prompt to redo the finished upgrade.
func TestMaybeRunPlugin_NoVersionCheckAfterSelfUpdate(t *testing.T) { //nolint:paralleltest // mutates PATH and the version-check seam
dir := t.TempDir()
writePluginBinary(t, dir, "entire-upgrade", filepath.Join(dir, "args.txt"), 0)
withPathDir(t, dir)
calls := interceptVersionCheck(t)

handled, code := MaybeRunPlugin(context.Background(), newTestRoot(), []string{"upgrade", "--nightly"})
 if !handled || code != 0 {
t.Fatalf("handled=%v code=%d, want handled=true code=0", handled, code)
 }
 if *calls != 0 {
t.Errorf("version check calls: got %d, want 0", *calls)
 }
}

func TestMaybeRunPlugin_NoVersionCheckAfterFailure(t *testing.T) { //nolint:paralleltest // mutates PATH and the version-check seam
dir := t.TempDir()
writePluginBinary(t, dir, "entire-pgr", filepath.Join(dir, "args.txt"), 3)
withPathDir(t, dir)
calls := interceptVersionCheck(t)

handled, code := MaybeRunPlugin(context.Background(), newTestRoot(), []string{"pgr"})
 if !handled || code != 3 {
t.Fatalf("handled=%v code=%d, want handled=true code=3", handled, code)
 }
 if *calls != 0 {
t.Errorf("version check calls: got %d, want 0", *calls)
 }
}

func equalStrings(a, b []string) bool {
 if len(a) != len(b) {
 return false