Skip to content

Allow permanently disabling lstk update checks - #491

Open
joe4dev wants to merge 6 commits into
mainfrom
devx-1029-update-check-opt-out
Open

Allow permanently disabling lstk update checks#491
joe4dev wants to merge 6 commits into
mainfrom
devx-1029-update-check-opt-out

Conversation

@joe4dev

@joe4dev joe4dev commented Sep 8, 2026

Copy link
Copy Markdown
Member

Motivation

lstk asks to update on every start with no way to turn it off. With weekly releases, "skip this version" buys a few days — the reporter sees the prompt almost daily.

It is also wrong when another tool owns the binary. mise, nix, asdf, scoop and chocolatey manage the version themselves; replacing it desynchronises them, and on nix the store is read-only so the update fails after a full download.

Solution

[cli] check_for_update_on_startup, also LSTK_CHECK_FOR_UPDATE_ON_STARTUP:

setting self-managed, interactive externally managed or non-interactive
true (default) today's blocking prompt one non-blocking note
false no check no check

A boolean rather than a prompt/notify/off enum (review): install detection already decides between a prompt and a note, so the only choice left to the user is whether to check at all. Externally-managed installs get the note and name the manager rather than advising lstk update. lstk update refuses on them (--force overrides); an explicit lstk update and lstk update --check are never gated by the setting.

Prompt: before / after

Before After
lstk-upadate-prompt-v1 lstk-update-prompt-v2
[S] Skip this version — per-version, buys days [N] Never check again — persists check_for_update_on_startup = false

[S] is removed rather than kept alongside [N]: it is the option the ticket says does not solve the problem, it was the third way to say "no" on one prompt, and it was the only per-version persisted state. cli.update_skipped_version goes with it; a leftover key is inert.

Docs

Docs needed

New user-facing surface to document:

  • [cli] check_for_update_on_startup config key and the LSTK_CHECK_FOR_UPDATE_ON_STARTUP environment variable (boolean, default true), and that neither gates an explicit lstk update.
  • Externally-managed installs (mise, nix, guix, asdf, scoop, chocolatey) default to notify and are not updated in place; lstk update --force overrides.
  • --json gains the UPDATE_EXTERNALLY_MANAGED error code (docs/structured-output.md updated in this PR).
  • The update prompt loses [S] Skip this version and gains [N] Never check again.
Manual testing (using fish shell) — 13 scenarios, all passing manual-testing

[!NOTE]
Captured before the boolean simplification, so the screenshot shows the old update_check key. Behaviour is unchanged apart from the key name and [N]'s persisted value; the recipe below is current.

Covered: enabled and disabled; env var overriding config; an invalid value rejected rather than coerced; a mise install naming the manager in its note; lstk update refusing on mise and nix (plain and --json); a read-only install directory refused; update --check still working when the check is disabled; [N] persisting the opt-out and the next run staying silent; the first-run prompt omitting [N]; and a mise install never prompting.

To replicate. Build a version-stamped binary (a dev build skips the check entirely) and point Docker at nothing, so start emits the update output and then fails at the health check without starting an emulator:

set SMOKE (mktemp -d)
go build -ldflags "-X github.com/localstack/lstk/internal/version.version=0.0.1" -o $SMOKE/bin/lstk .
set -x DOCKER_HOST tcp://localhost:1
printf '[[containers]]\ntype = "aws"\ntag  = "latest"\nport = "4566"\n\n[cli]\n' > $SMOKE/config.toml
set M $SMOKE/mise/.local/share/mise/installs/github-localstack-lstk/latest
mkdir -p $M; cp $SMOKE/bin/lstk $M/
# enabled / disabled / invalid
HOME=$SMOKE $SMOKE/bin/lstk --config $SMOKE/config.toml start --non-interactive        # note
HOME=$SMOKE LSTK_CHECK_FOR_UPDATE_ON_STARTUP=false $SMOKE/bin/lstk --config $SMOKE/config.toml start --non-interactive  # silent
HOME=$SMOKE LSTK_CHECK_FOR_UPDATE_ON_STARTUP=quiet $SMOKE/bin/lstk --config $SMOKE/config.toml start --non-interactive  # rejected

# externally managed
HOME=$SMOKE $M/lstk --config $SMOKE/config.toml start --non-interactive   # note names mise
HOME=$SMOKE $M/lstk update                                                # refuses
HOME=$SMOKE $M/lstk update --json                                         # UPDATE_EXTERNALLY_MANAGED

# interactive (needs a TTY)
HOME=$SMOKE $SMOKE/bin/lstk --config $SMOKE/config.toml start             # [U] / [R] / [N]

Pressing [U] performs a real download; set LSTK_UPDATE_GITHUB_API_ENDPOINT and LSTK_UPDATE_GITHUB_DOWNLOAD_ENDPOINT to a dead address to make it inert.

Interaction with #482 (bundled extensions)

#482 landed in parallel and touches the same file.

A silent one. detectMissingBundle guards on InstallBinary, so introducing InstallExternal would have quietly removed the missing-bundle hint for mise/nix/asdf installs — they used to classify as InstallBinary. It now covers external installs and names the managing tool (reinstall lstk through mise) instead of #482's release-download instruction, which would install outside the manager and be overwritten on its next sync.

Open questions

Where the update prompt sits in the start flow. NotifyUpdate runs as the first action of the start goroutine, ahead of the Docker health check, auth, and the emulator picker. On a genuine first run config.toml does not exist yet — the picker creates it — so [N] has nowhere to persist and is omitted rather than offered and silently dropped:

lstk-update-prompt-v2-first-run

The trade-off: moving the notification after the picker would make [N] available on every run, but prompting early is worth more — a user on an old or broken CLI should be offered the update before the CLI attempts real work, and a late prompt would be preempted by exactly the Docker failure an update might fix. The gap is narrow, since a first run almost always means a fresh install already on the latest version. Deliberately left as is; flagging it for a second opinion.

Review

Worth a human review. New user-facing surface (config key, env var, flag, error code) and a changed prompt, plus one behaviour change that is not opt-in: a detected mise/nix/asdf install gets a note instead of the prompt.

Automatic detection of externally managed (e.g., mise package installer) provides convenience by default, but add a bunch of complexity. The alternative would be solely relying on manual configuration.

Openspec preview: https://github.com/localstack/lstk/tree/4330e4d/openspec/changes/add-update-check-config

Related

#465 was an earlier, independent attempt at the same design, closed 2026-08-25.

#482 extending updates for extensions

Closes DEVX-1029

@joe4dev joe4dev added semver: minor docs: needed Pull request requires documentation updates labels Sep 8, 2026
Comment thread openspec/changes/add-update-check-config/proposal.md
@joe4dev
joe4dev marked this pull request as ready for review September 8, 2026 11:58
@joe4dev
joe4dev requested review from a team and peter-smith-phd as code owners September 8, 2026 11:58
@joe4dev
joe4dev force-pushed the devx-1029-update-check-opt-out branch from 7838fda to 6c6a856 Compare September 8, 2026 12:27
@joe4dev

joe4dev commented Sep 9, 2026

Copy link
Copy Markdown
Member Author

@gtsiolis What's your thought regarding the open question (see Section "Open questions")? Do you see a UX issue in having the [N] Never ask again option skipped at the first run?

@joe4dev

joe4dev commented Sep 9, 2026

Copy link
Copy Markdown
Member Author

@carillan81 What's your suggestion on how update notifications for bundled extensions work in this context (e.g., externally-managed lstk)? Do we to treat lstk and lstk extension updates separately? Happy to discuss.
⚠️ I think we should align #482 and this PR to avoid unintended interactions.

Copy link
Copy Markdown
Contributor

Extensions are only updated when lstk is updated. They are a package and there is no way to update extensions right now if they are not updated with lstk. That specific PR is specific to include extensions in the update process of a manually installed lstk. I don't see any need of notifications.

@joe4dev

joe4dev commented Sep 9, 2026

Copy link
Copy Markdown
Member Author

Extensions are only updated when lstk is updated. They are a package and there is no way to update extensions right now if they are not updated with lstk. That specific PR is specific to include extensions in the update process of a manually installed lstk. I don't see any need of notifications.

Great, thank you for the clarification @carillan81 👍

Then, our PRs complement each other nicely ✨

@anisaoshafi anisaoshafi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I didn't review in detail, but the overall logic looks good ✨
Thanks for the comprehensive set of screenshots in Manual testing section in PR description.

I also like the tradeoff you took on the open question to drop the option [N] when config.toml doesn't exist.

Comment thread internal/update/external_install.go
joe4dev and others added 4 commits September 11, 2026 15:36
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@joe4dev
joe4dev force-pushed the devx-1029-update-check-opt-out branch from 6c6a856 to 4e0b093 Compare September 11, 2026 13:39

@gtsiolis gtsiolis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks, @joe4dev! Added one suggestion, let me know what you think.

Comment thread internal/config/default_config.toml Outdated

# CLI behavior
[cli]
# update_check = "notify" # Update check on start: "prompt" (default), "notify", "off"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

suggestion: Could we simplify this to a boolean setting, like check_for_update_on_startup = false? When enabled, we could preserve the existing prompt for self-managed interactive installations and show a non-blocking notice for externally managed ones, as discussed in the relevant discussion. When disabled, it would skip the check entirely. This could cover the use cases without asking users to choose between prompting and notifying. 💭

Boolean behavior coverage:

Setting Self-managed, interactive Externally managed
true Prompt Non-blocking notice
false No check No check

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.

My thoughts:

  • Pros: Simpler user configuration with self-describing flag LSTK_CHECK_FOR_UPDATE_ON_STARTUP=0; no more magic [N] Never ask again conversion to notify
  • Con: Self-managed users (and failed detection of externally-managed) cannot opt into the non-blocking notify-only behavior anymore -> more users disable update check altogether

I think we should go for the simplification if we don't plan to offer a notify-only option.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I can’t think of a use case or user need for an explicit notify-only preference on self-managed installations. Externally managed installations would already get that behavior automatically.

I’d go with the simpler boolean for now and revisit if user feedback shows a need for more control. 💯

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 ✅
I migrated the config to use the simplified boolean approach:
[cli] check_for_update_on_startup and LSTK_CHECK_FOR_UPDATE_ON_STARTUP

Comment thread internal/config/default_config.toml Outdated
joe4dev and others added 2 commits September 11, 2026 18:15
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@joe4dev

joe4dev commented Sep 11, 2026

Copy link
Copy Markdown
Member Author

@carillan81 I discovered and fixed an interaction between the bundled extension (#482) and this PR (see PR description).

A silent one. detectMissingBundle guards on InstallBinary, so introducing InstallExternal would have quietly removed the missing-bundle hint for mise/nix/asdf installs — they used to classify as InstallBinary. It now covers external installs and names the managing tool (reinstall lstk through mise) instead of #482's release-download instruction, which would install outside the manager and be overwritten on its next sync.

Could you review this part and validate whether the adjusted message for externally-managed installations is appropriate?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs: needed Pull request requires documentation updates semver: minor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants