Skip to content

feat(update): self-update notifications and auto-update - #45

Merged
neurolabs merged 4 commits into
mainfrom
opencode/release-handling
Aug 31, 2026
Merged

feat(update): self-update notifications and auto-update#45
neurolabs merged 4 commits into
mainfrom
opencode/release-handling

Conversation

@neurolabs

Copy link
Copy Markdown
Member

Closes #31

Summary

Adds self-update support: run/shell check GitHub for a newer opencode-sandbox release and optionally install it.

Changes

  • internal/update package: Check (query releases/latest, compare versions, throttle, act per mode), Update (download opencode-sandbox-<os>-<arch> asset + atomic replace over the running executable), and persisted state (last-check time + per-version dismissals).
  • Config (config.yaml + OPENCODE_SANDBOX_UPDATE_* env): update.mode (prompt | notify | auto-upgrade | auto-upgrade-exit, default prompt) and update.interval (default 1d, minimum 1h to guard against GitHub rate limits).
  • upgrade command for on-demand installs.
  • Check hooks into run/shell (skipped on --dry-run); dev builds and transient network failures are skipped silently; non-interactive prompt falls back to a notice.
  • Docs (docs/configuration.md Self-update section, docs/commands.md) and CHANGELOG.md.

Testing

  • make check passes (fmt, golangci-lint 0 issues, go test ./...).
  • New unit tests for the update package, viperconfig update config/validation, and CLI upgrade tests.

run/shell check GitHub for a newer opencode-sandbox release (throttled to
update.interval, default 1d, minimum 1h). update.mode selects the behavior:
prompt (default), notify, auto-upgrade, or auto-upgrade-exit. Add an
'upgrade' command for on-demand installs.
@codecov

codecov Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.67972% with 29 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.83%. Comparing base (5a5897c) to head (efa366d).

Files with missing lines Patch % Lines
internal/upgrade/upgrade.go 86.05% 20 Missing and 9 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #45      +/-   ##
==========================================
+ Coverage   88.73%   88.83%   +0.10%     
==========================================
  Files          87       88       +1     
  Lines        5299     5553     +254     
==========================================
+ Hits         4702     4933     +231     
- Misses        414      431      +17     
- Partials      183      189       +6     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread cmd/opencode-sandbox/commands.go Outdated
if err != nil {
return err
}
if opencode.VersionCompare(version, latest) >= 0 {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use semver module for comparing versions?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done - version comparison now uses github.com/Masterminds/semver/v3 via upgrade.isNewer, and opencode.NewerThan for the runner-image opencode upgrade path.

Comment thread cmd/opencode-sandbox/commands.go Outdated
Args: cobra.NoArgs,
Short: "Check for and install the latest release",
RunE: func(cmd *cobra.Command, _ []string) error {
if version == devVersion {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

everything non-argparsing should be delegated to the update package

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done - buildUpgradeCmd now only parses args; the non-argparsing logic lives in upgrade.Upgrade.

Comment thread docs/commands.md

---

### upgrade

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update vs. upgrade naming clash. upgrade is better IMHO

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed update->upgrade across the package, config keys (upgrade.mode/upgrade.interval), env vars, state file, and docs for consistent naming.

Comment thread docs/configuration.md Outdated
The launcher validates:

- `cpus` must be between 0 and 255
- `update.mode` must be one of `prompt`, `notify`, `auto-upgrade`, `auto-upgrade-exit`

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

auto-upgrade -> auto
autp-upgrade-exit -> auto-exit

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done - the modes are now auto and auto-exit.

return Result{}, nil
}
interval := opts.Interval
switch {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fail on misconfiguration, otherwise users might not understand why updates are not happening at their configured frequency

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done - upgrade.Check now returns an error for an explicit sub-minimum interval instead of silently clamping it.

Comment thread internal/update/update.go Outdated
// the same filesystem and therefore atomic.
func replaceExecutable(assetPath, exePath string) error {
//nolint:gosec // G302: the downloaded release binary must be executable
if err := os.Chmod(assetPath, 0o755); err != nil {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The user might have installed it with different permissions, we should preserve the ones from the old binary

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done - replaceExecutable now preserves the old binary's mode instead of hardcoding 0o755.

- Rename update package/config to upgrade for consistent naming
- Compare versions with Masterminds/semver instead of custom comparison
- Delegate non-argparsing upgrade logic into the upgrade package
- Shorten modes: auto-upgrade->auto, auto-upgrade-exit->auto-exit
- Fail loudly on sub-minimum upgrade interval misconfiguration
- Preserve the old binary's permissions when replacing it
- Add tests covering previously-missing branches
@neurolabs
neurolabs merged commit e78b4f9 into main Aug 31, 2026
6 checks 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.

[Feature]: Notify of new releases / auto-update

1 participant