You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Rename the agent plugin basecamp → basecamp-cli - #840
This PR renames the CLI's Claude Code and Codex plugin to basecamp-cli@37signals, so the basecamp name is free for the hosted Basecamp connector plugin. The basecamp binary and the skill names are unchanged. Existing installs keep working.
Alias. The 37signals marketplace keeps basecamp as a deprecated alias of basecamp-cli, with the same source. Existing basecamp@37signals installs keep loading and updating.
Re-pointing basecamp at the hosted connector comes after that. It also needs a CLI release that stops treating basecamp@37signals as stale. Until then, setup would uninstall the connector. AGENTS.md and the ClaudeLegacyPluginKey comment record this.
Merge claude-plugins#8. It adds the basecamp-cli entry and keeps basecamp as an alias with the same source. Both serve the current plugin. That marketplace has no tags or releases; main is what clients fetch.
Merge this PR right after. The marketplace's unpinned url source serves main as soon as it lands. Installed copies don't move until the plugin version changes.
Cut the release with scripts/release.sh. Its release-prep commit bumps .claude-plugin/plugin.json and .codex-plugin/plugin.json, then it tags v* and release.yml publishes. The bump delivers the renamed manifest and the notice to basecamp@37signals installs. The 7-day window starts here. This build installs basecamp-cli@37signals, which is why Add built-in OAuth credentials for production Launchpad #8 has to merge first.
Only then submit the hosted plugin (plugins/basecamp in claude-plugins) to Anthropic's directory, which publishes it as basecamp. Before this release, CLI plugin installs still carry the manifest name basecamp, and the two would collide.
Manifests..claude-plugin/plugin.json and .codex-plugin/plugin.json use name: basecamp-cli. Setup installs basecamp-cli@37signals, and doctor checks for it.
Migration by key. During the window, basecamp@37signals only ever means this plugin.
Claude: setup treats it as a stale entry, like basecamp@basecamp. It uninstalls it per scope and reinstalls basecamp-cli at those scopes.
Codex: setup adds basecamp-cli first, then removes the old ID.
Doctor: reports the old name, and warns when both copies are installed.
One-time notice for alias users who never run setup. A SessionStart hook detects an install under the old ID from its plugin root (.../37signals/basecamp/<version>, via CLAUDE_PLUGIN_ROOT or Codex's PLUGIN_ROOT). It can't use the manifest, because the alias and basecamp-cli install identical files. Once per plugin data dir, it emits a systemMessage and context saying "The Basecamp plugin is now basecamp-cli … Run basecamp setup claude|codex to switch." Every other install stays silent.
The hook runs basecamp agent-hook pre-commit-snapshot, not a new subcommand. Every CLI that supports hooks already has that subcommand, and older versions ignore a SessionStart payload. A new subcommand made an older CLI fail every session start with unknown command; I observed this with 0.11.0. The release test allows that one SessionStart hook and nothing else, so the "no context injection" rule still holds.
End-to-end test (throwaway Claude config: HOME/CLAUDE_CONFIG_DIR in a temp dir, Claude Code 2.1.290)
Installed basecamp@37signals from claude-plugins main (9dd90cb): 0.11.0, enabled.
Pointed a local marketplace copy at this branch. It has basecamp-cli plus a basecamp alias with the same url source (a local clone with the version bumped to simulate a release). Ran marketplace update and plugin update basecamp@37signals: updated 0.11.0 → 0.12.x, still basecamp@37signals, still enabled. plugin details lists the skills and 4 hooks. So an install under the old ID loads the renamed manifest without trouble.
This build: first session prints the notice (exit 0), second session prints nothing.
basecamp doctor reported "Installed under the old name basecamp@37signals". basecamp setup claude left only basecamp-cli@37signals (user scope, 0.12.x). After that, doctor passes and the hook stays silent.
claude plugin validate passes on the plugin manifest. On the alias marketplace it passes with the existing "no marketplace description" warning.
Effect on users
Plugin skills are namespaced by plugin name, so basecamp:basecamp becomes basecamp-cli:basecamp after switching. The ~/.claude/skills/basecamp link is unchanged.
Codex treats hooks as untrusted until the user reviews them in /hooks. The new SessionStart hook won't run there until trusted, but Codex setup and doctor still migrate.
Testing
Unit tests cover:
the stale legacy key and the doctor messages;
Claude setup migrating at the same scopes;
Codex setup adding before removing, keeping the old ID while basecamp-cli is installed but disabled, and its removal-failure remediation;
the notice firing once for Claude and Codex roots, staying silent otherwise, and a SessionStart payload taking no snapshot;
Renames the Claude and Codex plugin to basecamp-cli@37signals and migrates legacy installs safely.
Changes:
Updates manifests, setup commands, diagnostics, and documentation.
Detects legacy CLI plugins through repository manifests.
Adds migration and release-validation tests.
[!TIP]
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.
File
Description
README.md
Documents renamed plugin and migration.
install.md
Updates installation guidance.
AGENTS.md
Records integration naming and migration rules.
.claude-plugin/plugin.json
Renames the Claude plugin.
.codex-plugin/plugin.json
Renames the Codex plugin.
internal/harness/claude.go
Detects and classifies legacy Claude installs.
internal/harness/claude_test.go
Tests Claude detection and migration cases.
internal/harness/codex.go
Detects legacy cached Codex installs.
internal/harness/codex_test.go
Tests Codex legacy detection and diagnostics.
internal/commands/wizard_agents.go
Updates Claude setup labels and installation behavior.
Verify replacement plugin before removing legacy entry
internal/commands/wizard_codex.go:122
This gate proves only that the legacy copy exists, not that the replacement is installed and enabled. The later verification already accounts for plugin add returning success without producing the expected installed state; if that happens while the legacy entry is still listed, this code removes the working legacy copy and only then reports verification failure, leaving neither usable. Verify the new plugin before removal, remove the legacy key only when the replacement is healthy, and retain the final post-removal check.
Verify replacement plugin before removing legacy copy
internal/commands/wizard_codex.go:118
Verify that the replacement is installed and enabled before deleting the legacy copy. plugin add errors containing “already installed” are accepted above, so an existing but disabled basecamp-cli can reach this branch; CodexLegacyCLIInstalled only reports the old manifest and will remove the still-working legacy plugin, after which the final verification fails. Gate removal on CheckCodexPluginContext returning pass or warn first, then retain the final post-removal check.
Fix cross-reference to legacyCLIPluginEntries
internal/harness/claude.go:58
This cross-reference names legacyCLIInstall, which does not exist; the detection helper introduced below is legacyCLIPluginEntries. Pointing to the actual symbol keeps the safety rationale traceable.
Redesign (d3cc1e5). The review rounds on this PR all came back to one problem: telling the pre-rename CLI plugin apart from the hosted connector when both use basecamp@37signals. The fixes for it were the manifest/repository check, scope fail-closed, projectPath matching, CODEX_HOME cache reads, and keeping Codex remediation behind setup. Each round added another one. Instead of patching further, this removes the ambiguity: during the rename, the 37signals marketplace (basecamp/claude-plugins#8) lists basecamp-cli and nobasecamp plugin, so basecamp@37signals can only be this CLI's old plugin and setup replaces it by key. The heuristics are deleted (net −290 lines).
Each case the heuristics handled, and where it lands now:
User scope: the existing stale path runs uninstall --scope user and reinstalls basecamp-cli at user scope.
Project/local scope (the projectPath thread): the scoped uninstall runs from the current directory. If that directory's entry is the old plugin, it is removed and reinstalled there. If the entry belongs to another checkout, the uninstall fails harmlessly and nothing is reinstalled. No hosted connector can be under this key, so neither outcome is destructive. Running setup in that checkout migrates it.
Missing or invalid scope (cubic P1): the unscoped fallback can only remove old-plugin installs now, so it is safe again.
v1 / array formats (the declined cubic thread): stalePluginKeys already parses these, so they are now migrated by key too.
Codex: legacy detection comes from codex plugin list alone. Setup adds basecamp-cli before removing the old plugin. The failure remediation is codex plugin remove basecamp@37signals again, which is safe now.
CODEX_HOME: it only mattered for reading cached manifests, which are no longer read.
Disabled install beside the old one: doctor still mentions the old copy.
Doctor: shows the old name when only the old plugin is installed. Claude and Codex warn when both copies are installed.
Manual README steps (the Copilot threads): uninstalling or removing by key is safe again. The README now installs first and then removes.
Follow-up constraint: re-adding basecamp to our marketplace for the hosted plugin is a later step. It waits for a deprecation window and for a CLI release that drops the basecamp@37signals stale handling. AGENTS.md and the ClaudeLegacyPluginKey comment record this.
The 37signals marketplace gives the basecamp name to the hosted Basecamp
connector plugin, so this CLI's Claude Code and Codex plugin becomes
basecamp-cli@37signals. The CLI binary and skill names are unchanged.
setup claude and setup codex migrate a pre-rename install: an entry under
basecamp@37signals whose installed manifest names this repository is
replaced with basecamp-cli@37signals (Claude at the same scopes; Codex
removes the old copy only after the new one installs). An entry whose
manifest names anything else, or can't be read, is the hosted connector or
unknown, and is left alone. doctor reports the old name instead of
'Plugin not installed'.
… leftover copies
A legacy basecamp@37signals entry counts only at a scope claude plugin
uninstall --scope accepts, so setup can't fall back to an unscoped
uninstall of the shared key that would also remove a hosted connector.
doctor warns when the old copy is still installed next to basecamp-cli
(Claude), or alongside a disabled one (Codex). The README's manual steps
install first and remove the old key only after checking its manifest.
…elongs to
Claude resolves project and local scopes against the working directory, so
an uninstall run from another checkout would reach that checkout's entry
under the shared key, which may be the hosted connector. Such entries now
count as legacy only when their projectPath is the current directory.
…by manifest
The 37signals marketplace now lists no basecamp plugin during the rename, so
basecamp@37signals can only be this CLI's pre-rename plugin. Setup removes
it by key through the existing stale-entry path, and doctor reports it,
without the manifest, scope and projectPath checks that existed only to
tell it apart from a hosted connector under the same key. Re-adding
basecamp to the marketplace has to wait until this handling is dropped.
…i, once
During the deprecation window the marketplace keeps basecamp as an alias of
basecamp-cli with the same source, so existing installs keep working. A
SessionStart hook, agent-hook plugin-notice, recognizes an install under the
old id by its plugin root (.../37signals/basecamp/<version>), since the
manifest is the same for both, and says once that the plugin is now
basecamp-cli and which setup command switches it. Any other install stays
silent.
…tay silent
A new agent-hook subcommand fails with unknown command, on every session
start, for anyone whose CLI is older than the plugin. pre-commit-snapshot
exists in every hook-capable CLI and ignores a payload with no tool call,
so the SessionStart hook runs it and only CLIs that know the notice act on
the SessionStart event.
Codex keeps a disabled plugin installed, so setup could remove the working
basecamp@37signals while basecamp-cli sat disabled beside it. Setup now
removes the old ID only when the replacement is installed and enabled.
Also correct the legacy-key comments: during the window the marketplace
lists basecamp as an alias of basecamp-cli, not nothing at all.
Install the Claude replacement before removing: remove-first is what keeps project/local migration in the checkout it belongs to. This also answers the same point in Copilot's review body on 40af6c8 ("Install replacement before removing legacy plugin").
Honor CLAUDE_CONFIG_DIR: every Claude path in this CLI reads ~/.claude, so supporting the overrides is a CLI-wide change for its own PR.
Also in 917e451: the legacy-key comments now say the marketplace lists basecamp as an alias during the window. They used to say it lists no basecamp plugin.
Window: 7 days from the release that ships this, and the description now says so (it used to say 30). #857 tracks retiring the alias. The description also gives the merge and release order with basecamp/claude-plugins#8.
Copilot couldn't review dcd43bb or 917e451 because the requester hit its quota limit. Its last full review was on 795b275. A re-review has to be requested from the Reviewers UI.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR renames the CLI's Claude Code and Codex plugin to
basecamp-cli@37signals, so thebasecampname is free for the hosted Basecamp connector plugin. Thebasecampbinary and the skill names are unchanged. Existing installs keep working.Deprecation window (with basecamp/claude-plugins#8)
basecampas a deprecated alias ofbasecamp-cli, with the same source. Existingbasecamp@37signalsinstalls keep loading and updating.basecampat the hosted connector comes after that. It also needs a CLI release that stops treatingbasecamp@37signalsas stale. Until then, setup would uninstall the connector. AGENTS.md and theClaudeLegacyPluginKeycomment record this.Merge and release order
Merge this together with basecamp/claude-plugins#8, #8 first:
basecamp-clientry and keepsbasecampas an alias with the same source. Both serve the current plugin. That marketplace has no tags or releases;mainis what clients fetch.urlsource servesmainas soon as it lands. Installed copies don't move until the plugin version changes.scripts/release.sh. Its release-prep commit bumps.claude-plugin/plugin.jsonand.codex-plugin/plugin.json, then it tagsv*andrelease.ymlpublishes. The bump delivers the renamed manifest and the notice tobasecamp@37signalsinstalls. The 7-day window starts here. This build installsbasecamp-cli@37signals, which is why Add built-in OAuth credentials for production Launchpad #8 has to merge first.plugins/basecampin claude-plugins) to Anthropic's directory, which publishes it asbasecamp. Before this release, CLI plugin installs still carry the manifest namebasecamp, and the two would collide.basecamp@37signalsstale handling.What changes
.claude-plugin/plugin.jsonand.codex-plugin/plugin.jsonusename: basecamp-cli. Setup installsbasecamp-cli@37signals, and doctor checks for it.basecamp@37signalsonly ever means this plugin.basecamp@basecamp. It uninstalls it per scope and reinstallsbasecamp-cliat those scopes.basecamp-clifirst, then removes the old ID..../37signals/basecamp/<version>, viaCLAUDE_PLUGIN_ROOTor Codex'sPLUGIN_ROOT). It can't use the manifest, because the alias andbasecamp-cliinstall identical files. Once per plugin data dir, it emits asystemMessageand context saying "The Basecamp plugin is nowbasecamp-cli… Runbasecamp setup claude|codexto switch." Every other install stays silent.basecamp agent-hook pre-commit-snapshot, not a new subcommand. Every CLI that supports hooks already has that subcommand, and older versions ignore a SessionStart payload. A new subcommand made an older CLI fail every session start withunknown command; I observed this with 0.11.0. The release test allows that one SessionStart hook and nothing else, so the "no context injection" rule still holds.End-to-end test (throwaway Claude config:
HOME/CLAUDE_CONFIG_DIRin a temp dir, Claude Code 2.1.290)basecamp@37signalsfrom claude-plugins main (9dd90cb): 0.11.0, enabled.basecamp-cliplus abasecampalias with the sameurlsource (a local clone with the version bumped to simulate a release). Ranmarketplace updateandplugin update basecamp@37signals: updated 0.11.0 → 0.12.x, stillbasecamp@37signals, still enabled.plugin detailslists the skills and 4 hooks. So an install under the old ID loads the renamed manifest without trouble.claude -p --include-hook-events):basecamp doctorreported "Installed under the old name basecamp@37signals".basecamp setup claudeleft onlybasecamp-cli@37signals(user scope, 0.12.x). After that, doctor passes and the hook stays silent.claude plugin validatepasses on the plugin manifest. On the alias marketplace it passes with the existing "no marketplace description" warning.Effect on users
basecamp:basecampbecomesbasecamp-cli:basecampafter switching. The~/.claude/skills/basecamplink is unchanged./hooks. The new SessionStart hook won't run there until trusted, but Codex setup and doctor still migrate.Testing
basecamp-cliis installed but disabled, and its removal-failure remediation;