From 94d4c0be28ca0eb2f91ab2bfa75999075469fcb7 Mon Sep 17 00:00:00 2001 From: Rob Zolkos Date: Sun, 1 Nov 2026 11:35:13 -0500 Subject: [PATCH 1/2] Model a signed-in Codex in the setup and doctor tests A signed-in Codex answers `plugin list --available --json` only after it fetches the remote openai-curated catalog, which on a cold cache takes about 35 seconds. The Codex stub now answers only the listing scoped to the 37signals marketplace, and these tests fail the way a new machine does: setup installs the plugin, then reports "Cannot query Codex plugins". --- internal/commands/doctor_test.go | 2 +- internal/commands/wizard_codex_test.go | 9 ++++++--- internal/harness/codex_test.go | 6 +++--- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/internal/commands/doctor_test.go b/internal/commands/doctor_test.go index 8cf201512..c2ef36e34 100644 --- a/internal/commands/doctor_test.go +++ b/internal/commands/doctor_test.go @@ -949,7 +949,7 @@ func TestCheckCodexIntegrationIncludesVersion(t *testing.T) { } } require.Equal(t, []string{"Codex Plugin", "Codex Plugin Version"}, names) - assert.Equal(t, 1, strings.Count(readCodexSetupCalls(t, logPath), "plugin list --available --json")) + assert.Equal(t, 1, strings.Count(readCodexSetupCalls(t, logPath), "plugin list --available --json --marketplace 37signals")) } func TestCheckCodexIntegrationWarnsOnVersionMismatch(t *testing.T) { diff --git a/internal/commands/wizard_codex_test.go b/internal/commands/wizard_codex_test.go index 522d6ef53..15f77ddbe 100644 --- a/internal/commands/wizard_codex_test.go +++ b/internal/commands/wizard_codex_test.go @@ -44,7 +44,7 @@ func TestSetupCodexFreshInstallCommandOrder(t *testing.T) { assertCallOrder(t, calls, "plugin marketplace add basecamp/claude-plugins --json", "plugin add basecamp-cli@37signals --json", - "plugin list --available --json", + "plugin list --available --json --marketplace 37signals", ) } @@ -96,7 +96,7 @@ func TestSetupCodexReplacesLegacyCLIPlugin(t *testing.T) { assertCallOrder(t, readCodexSetupCalls(t, logPath), "plugin add basecamp-cli@37signals --json", "plugin remove basecamp@37signals --json", - "plugin list --available --json", + "plugin list --available --json --marketplace 37signals", ) } @@ -324,7 +324,10 @@ func installCodexStub(t *testing.T, options codexStubOptions) string { " \"plugin remove basecamp@37signals --json\")\n" + " if [ " + boolShell(options.legacyRemoveFailure) + " = 1 ]; then echo 'remove failure' >&2; exit 1; fi\n" + " echo removed > \"" + legacyPath + "\"; echo '{}'; exit 0 ;;\n" + - " \"plugin list --available --json\")\n" + + // Unfiltered, a signed-in Codex also fetches the remote openai-curated + // catalog, which on a cold cache outlasts every probe deadline, so the + // stub answers only the listing scoped to our marketplace. + " \"plugin list --available --json --marketplace 37signals\")\n" + " enabled=true; if [ " + boolShell(options.pluginDisabled) + " = 1 ]; then enabled=false; fi\n" + " legacy_state=''; [ -f \"" + legacyPath + "\" ] && read -r legacy_state < \"" + legacyPath + "\"\n" + " if [ \"$legacy_state\" = installed ]; then legacy='{\"pluginId\":\"basecamp@37signals\",\"version\":\"0.11.0\",\"installed\":true,\"enabled\":true},'; else legacy=''; fi\n" + diff --git a/internal/harness/codex_test.go b/internal/harness/codex_test.go index a37255be5..334836ab0 100644 --- a/internal/harness/codex_test.go +++ b/internal/harness/codex_test.go @@ -90,7 +90,7 @@ func TestCheckCodexPluginCommandFailure(t *testing.T) { assert.Equal(t, "fail", check.Status) assert.Contains(t, check.Message, "Cannot query") - assert.Contains(t, check.Hint, "codex plugin list --available --json") + assert.Contains(t, check.Hint, "codex plugin list --available --json --marketplace 37signals") } func TestCheckCodexPluginVersionMatching(t *testing.T) { @@ -157,7 +157,7 @@ func TestCheckCodexPluginUsesSupportedJSONCommand(t *testing.T) { assert.Equal(t, "pass", check.Status) assert.Equal(t, "/usr/local/bin/codex", gotPath) - assert.Equal(t, []string{"plugin", "list", "--available", "--json"}, gotArgs) + assert.Equal(t, []string{"plugin", "list", "--available", "--json", "--marketplace", "37signals"}, gotArgs) } func TestCodexAgentInfoWiring(t *testing.T) { @@ -198,7 +198,7 @@ func stubCodexList(t *testing.T, output string, commandErr error) { original := runCodexCommand runCodexCommand = func(_ context.Context, path string, args ...string) ([]byte, error) { assert.Equal(t, "/usr/local/bin/codex", path) - assert.Equal(t, []string{"plugin", "list", "--available", "--json"}, args) + assert.Equal(t, []string{"plugin", "list", "--available", "--json", "--marketplace", "37signals"}, args) return []byte(output), commandErr } t.Cleanup(func() { runCodexCommand = original }) From cc8181fd7153070f83a5932b269fd591a976615c Mon Sep 17 00:00:00 2001 From: Rob Zolkos Date: Sun, 1 Nov 2026 11:44:40 -0500 Subject: [PATCH 2/2] Scope the Codex plugin probe to the 37signals marketplace On a signed-in Codex, `codex plugin list --available --json` first fetches the remote openai-curated catalog: about 35 seconds and 10,000+ plugins on a cold cache. The probe's 5 second deadline killed it before Codex wrote that cache, so setup verification and doctor failed with "Cannot query Codex plugins" on every run, even though the plugin had installed. Asking for the 37signals marketplace alone answers in tens of milliseconds and returns both plugin IDs the checks read. `--marketplace` has been on `codex plugin list` since before `--available`, so every Codex that ran the old probe runs this one. --- internal/harness/codex.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/internal/harness/codex.go b/internal/harness/codex.go index 9c7776ef2..2d051b468 100644 --- a/internal/harness/codex.go +++ b/internal/harness/codex.go @@ -299,7 +299,10 @@ func queryCodexPlugin(parent context.Context) (codexPluginState, bool, error) { } ctx, cancel := context.WithTimeout(parent, codexQueryTimeout) defer cancel() - data, err := runCodexCommand(ctx, path, "plugin", "list", "--available", "--json") + // Unscoped, a signed-in Codex first fetches the remote openai-curated + // catalog, about 35s on a cold cache. The deadline kills it before the + // cache is written, so every later probe would time out too. + data, err := runCodexCommand(ctx, path, "plugin", "list", "--available", "--json", "--marketplace", CodexMarketplaceName) if err != nil { return codexPluginState{}, false, fmt.Errorf("query Codex plugins: %w", err) } @@ -362,7 +365,7 @@ func codexQueryFailure(name string, err error) *StatusCheck { Name: name, Status: "fail", Message: "Cannot query Codex plugins", - Hint: "Run `codex plugin list --available --json`, then: basecamp setup codex", + Hint: "Run `codex plugin list --available --json --marketplace " + CodexMarketplaceName + "`, then: basecamp setup codex", } } message := "Cannot query Codex plugins" @@ -373,6 +376,6 @@ func codexQueryFailure(name string, err error) *StatusCheck { Name: name, Status: "fail", Message: message, - Hint: "Run `codex plugin list --available --json`, then: basecamp setup codex", + Hint: "Run `codex plugin list --available --json --marketplace " + CodexMarketplaceName + "`, then: basecamp setup codex", } }