Skip to content

Add a global --verbose flag so error output can lead with the fix #45

Description

@Higangssh

Follow-up to #41. That issue raised two things; the remote half was a real bug and is
fixed in #44. This is the other half, which needs a design decision rather than a
message tweak.

The problem

Reported in #41: when a command fails on a permission error, the raw Go error leads and
the actionable hint trails behind it.

upgrading local... ✗ cannot backup current binary: rename /usr/local/bin/homebutler /usr/local/bin/homebutler.bak: permission denied

  ⚠️  Try: sudo homebutler upgrade

The hint is correct. The complaint is about ordering and noise — the only thing the user
needed was "rerun with sudo", and rename /usr/local/bin/homebutler /usr/local/bin/homebutler.bak: permission denied tells them nothing they can act on.

The suggestion in #41 was to show just the fix and keep the raw error for verbose mode.
The second half is the blocker: there is no --verbose/--debug flag today, so dropping
the raw error would make it unrecoverable. I'd rather add the flag than lose the detail.

Scope

This is not upgrade-specific. The same raw error + hint shape is used in nine places:

  • internal/remote/upgrade.go:107,119 — backup/write binary
  • internal/backup/backup.go:79,85 — create backup/compose dir
  • internal/backup/restore.go:127 — create bind mount dir
  • internal/install/install.go:39,45,63,683,692 — registry and app dirs
  • cmd/ports.go:31 — missing process names

Whatever we decide should apply to all of them, otherwise the output gets inconsistent.

Why it isn't a one-line change

The messages are built inside internal/*, but a flag lives in cmd/. So this needs a
way for the internal packages to know the verbosity level. Options:

  1. A package-level setting in internal/util that cmd/root.go sets before dispatch.
    Simplest, but it's global mutable state.
  2. Thread a verbosity value through the call sites. Cleanest, but touches a lot of
    signatures for a presentation concern.
  3. Keep returning the full error and strip the raw part at the presentation layer. Needs
    the two parts to be separable — a typed error carrying {cause, hint} rather than a
    pre-formatted string.

I lean toward (3): a small error type with a hint field, formatted at the point of
display. It fixes the ordering problem without any global state, and --verbose just
selects a different formatter. util.IsPermissionError already exists and is what gates
the hint today, so the detection side is done.

Notes

  • The flag goes in cmd/root.go:30-34 alongside --json, --server, --all, --config.
  • maybeRouteRemote strips --server, --all, --config before forwarding argv to a
    remote host (cmd/root.go:79,88). --verbose should not be stripped — if you ask
    for detail you want it from the remote too.
  • --json output should be unaffected; it isn't the human-facing path.
  • Naming: --verbose/-v collides with the usual short flag for version. homebutler version is a subcommand here, not a flag, so -v looks free — worth double-checking
    before claiming it.

Proposed default output

upgrading local... ✗ permission denied — rerun with: sudo homebutler upgrade

with the current full text available under --verbose.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions