Skip to content

feat: keep installed CLI up to date with background self-upgrade#47

Merged
scotthavird merged 4 commits into
mainfrom
claude/support-envelope-v1.2-Gzsge
May 11, 2026
Merged

feat: keep installed CLI up to date with background self-upgrade#47
scotthavird merged 4 commits into
mainfrom
claude/support-envelope-v1.2-Gzsge

Conversation

@scotthavird
Copy link
Copy Markdown
Contributor

@scotthavird scotthavird commented May 10, 2026

Summary

  • New promptconduit upgrade subcommand (--check, --force) that downloads the matching os/arch release archive, verifies SHA256 against checksums.txt, and atomically replaces the running binary.
  • New internal/updater package (stdlib-only — no new module deps) that handles version checks, asset selection, archive extraction (tar.gz and zip), and platform-aware self-replacement (kernel-friendly overwrite on Linux/macOS; .old-aside trick on Windows, cleaned up on next launch).
  • Background check wired into rootCmd.PersistentPreRun. Once every 24h on top-level commands, if a newer release is found it prints a one-line notice with the release-notes URL to stderr and (unless the user opted out) spawns a detached promptconduit upgrade subprocess so the swap happens in the background — the foreground command is never blocked beyond a 5s timeout, and the running process keeps its inode and finishes unaffected.
  • One-time upgraded vX → vY notice the first run after a self-replace, gated on a real forward step so a downgrade (e.g. brew install --version older build) is silent rather than mislabelled as an upgrade.
  • File lock at ~/.config/promptconduit/upgrade.lock so concurrent invocations don't race on the swap; stale locks (>10m) are reclaimed automatically.
  • Skipped for the hook subcommand (must stay fast — it runs per event), the upgrade subcommand (checks itself), dev builds (Version=="dev"), and when the install dir is not writable (Homebrew users see brew upgrade promptconduit instead).
  • Opt-out: promptconduit config set --disable-auto-update=true or PROMPTCONDUIT_AUTO_UPDATE=0. Check result is cached at ~/.config/promptconduit/update.json.
  • config show reports auto-update state; config set --disable-auto-update=<bool> toggles it.
  • README + CLAUDE.md updated with a "Staying up to date" section.

Legacy cleanup (in this PR)

Separate commit removes dead send paths that no longer have any caller:

  • /v1/prompts/ingest-multipart chain (SendPromptWith*, SendPromptDirect, hook --send-prompt, PromptMetadata/GitMetadata/SerializedAttachment, and stale hook helpers isPromptEvent/getPromptText/buildPromptMetadata).
  • Legacy structured /v1/transcripts/sync path (SyncTranscript, TranscriptSyncRequest/Conversation/Message, buildSyncRequest).

All sends now go through the raw-envelope endpoint and the raw-JSONL transcript sync. Net −479 lines in that commit.

Test plan

  • go build ./..., go vet ./..., go test ./... all clean.
  • Unit tests in internal/updater: semver compare (incl. pre-release/build suffix stripping and unparseable inputs), ShouldCheck TTL + version-mismatch invalidation, asset/checksum selection, the "no checksums.txt = refuse" guard, LoadCache corrupt-JSON guard, and DetectUpgrade (forward/equal/downgrade/dev/nil cases).
  • Integration tests with httptest.Server: CheckLatest happy path / equal version / non-200; end-to-end download → checksum verify → atomic swap; checksum-mismatch rejection; lock acquire / release / stale-claim.
  • cmd/root_test.go covers skipUpdateCheckFor for hook/upgrade/nested subcommands and the PROMPTCONDUIT_AUTO_UPDATE_CHILD env override.
  • Smoke test: help/flags, config show reports Auto-update: enabled/disabled, config set --disable-auto-update round-trips, hook skips the check, post-upgrade notice fires on a simulated upgrade and is silent on a simulated downgrade.
  • Test on Windows runner that .old cleanup works after a real swap. (replaceBinary Windows branch is covered by CleanupOldBinary semantics but the actual swap isn't exercised on a Windows runner.)

https://claude.ai/code/session_019CWBC2E8pQuShejfsKYrvp

claude added 4 commits May 10, 2026 22:11
Adds an `upgrade` subcommand and a once-per-day background version check
wired into the root command's PersistentPreRun. When a newer release is
found on GitHub:

  - prints a one-line notice to stderr
  - by default spawns a detached `promptconduit upgrade` that downloads
    the os/arch archive, verifies SHA256 against checksums.txt, and
    atomically swaps the running binary (kernel keeps the open inode on
    Linux/macOS; Windows renames .old aside and cleans up next run)
  - is skipped for the `hook` subcommand to keep per-event latency low
  - is skipped on dev builds and when running under a non-writable
    install path (e.g. Homebrew), with a hint to use `brew upgrade`

Users can opt out via `promptconduit config set --disable-auto-update=true`
or `PROMPTCONDUIT_AUTO_UPDATE=0`. The check result is cached at
`~/.config/promptconduit/update.json` so subsequent commands within 24h
re-use the answer without hitting the GitHub API.

The `updater` package is stdlib-only — no new module dependencies.

https://claude.ai/code/session_019CWBC2E8pQuShejfsKYrvp
…ock + integration tests

Round of DX polish on the auto-update flow:

- Notification now includes the release-notes URL so users can see what's
  in the upgrade without leaving their terminal. Drops the misleading
  "run `promptconduit version` to confirm" hint — the background swap is
  async so the very next foreground command is what actually shows the
  new version.
- One-time success notice: when the running binary's version doesn't
  match the cache's recorded current_version, we just got swapped, so
  print `upgraded vX → vY` plus the release URL and rewrite the cache so
  the notice doesn't repeat.
- File lock at `~/.config/promptconduit/upgrade.lock` (O_CREATE|O_EXCL)
  so two concurrent `promptconduit X` invocations don't both spawn an
  upgrade subprocess and race on the binary swap. Stale locks (>10m,
  longer than DownloadTimeout) are reclaimed automatically.
- Integration tests with httptest.Server: end-to-end download + checksum
  verify + atomic swap, plus lock acquire/release/stale-claim coverage.

https://claude.ai/code/session_019CWBC2E8pQuShejfsKYrvp
The CLI grew two pairs of "old vs new" send paths during the migration to
the raw-envelope format. Nothing calls the legacy variants anymore, so
delete them:

  - /v1/prompts/ingest-multipart (PromptMetadata, PromptContextMetadata,
    GitMetadata, SerializedAttachment, SendPromptWithAttachments[Async],
    SendPromptDirect, sendPromptAsync*/Blocking, and the
    `hook --send-prompt` subcommand + sendPromptFromStdin handler).
    Prompt+attachment capture goes through SendEnvelopeWithAttachments
    on /v1/events/raw instead.
  - /v1/transcripts/sync structured path (SyncTranscript, TranscriptSync-
    Request, TranscriptConversation, TranscriptMessage, and the
    `buildSyncRequest` helper). All sync calls go through
    SyncTranscriptRaw (/v1/transcripts/sync/raw) or the chunked upload,
    which both send raw JSONL for server-side categorization.
  - Stale hook.go helpers (isPromptEvent, getPromptText,
    buildPromptMetadata) that only fed those dead paths.

Breaking-change OK per maintainer — the legacy server endpoints can be
retired now that the CLI no longer hits them.

https://claude.ai/code/session_019CWBC2E8pQuShejfsKYrvp
The post-upgrade success notice fired whenever the running binary's
version differed from the cache, including downgrades. After e.g. `brew
install --version v0.3.1` against a cache that recorded v0.3.2, the
"upgraded v0.3.2 → v0.3.1" line is misleading. Gate on a real forward
step via a new `CheckResult.DetectUpgrade` helper.

Test coverage additions:

  - DetectUpgrade table test (forward, equal, downgrade, dev/unparseable,
    nil/empty cache).
  - CheckLatest end-to-end via httptest.Server (releaseAPIURL is now a
    var so tests can point it at the local mock): happy path returns
    newer, equal version returns not-newer, non-200 surfaces an error.
  - LoadCache corrupt-JSON guard, and confirmation that ShouldCheck
    still returns true (so the next invocation re-checks instead of
    wedging on a corrupt cache).
  - cmd/root_test.go covers `skipUpdateCheckFor` for hook/upgrade/nested
    subcommands and the PROMPTCONDUIT_AUTO_UPDATE_CHILD env override.

https://claude.ai/code/session_019CWBC2E8pQuShejfsKYrvp
@scotthavird scotthavird marked this pull request as ready for review May 11, 2026 00:48
@scotthavird scotthavird merged commit 99d4f63 into main May 11, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants