Skip to content

BenchBar 0.7.3

Latest

Choose a tag to compare

@github-actions github-actions released this 04 Oct 07:16
· 9 commits to main since this release
3f2d406

Update

Already using BenchBar? In the app, click Update Now in the update banner or the menu bar menu (BenchBar 0.6 and later check once a day). On 0.5.x, run this in Terminal:

curl -fsSL https://raw.githubusercontent.com/askysh/benchbar/main/install.sh | bash -s -- --yes

It updates the benchbar CLI in ~/.local/share/benchbar and the app in ~/Applications, quits a running BenchBar first and never touches a bench. From 0.6 on, benchbar self-update runs the same. If your benchbar is a git checkout of your own, git pull there and add --app-only.

Installed with Homebrew? Run brew upgrade, or click Update Now in the app: from 0.7.1 the app carries its command line tool, and Homebrew's benchbar hands off to it, so either way updates both. The tap gets a release a few minutes after it is published.

Changes

A fix pass from a full review: benchbar stops only what it can prove is a
bench's own, fails closed on every write, moves v15-lts to node@22 before
Homebrew disables node@20 on 2026-10-28, installs cleanly on a fresh
Homebrew MariaDB and next to other benches, and hardens the MCP server.
Run benchbar repair once after updating: the runner and the helper block
are rewritten.

Fixed

  • The runner's pre-start cleanup stops only listeners whose working
    folder is this bench; another bench's Redis or web server and any
    other program on the same ports are left alone. When such a process
    still holds a port, the runner pauses with the new stop reason
    port_conflict instead of starting honcho into ports it cannot bind
    (run benchbar doctor).
  • benchbar down no longer signals a process whose working folder lsof
    cannot read; doctor's orphans check names such a process and says why
    it is not stopped, and ports plan counts it as a conflict, as up
    already did.
  • benchbar up on a bench whose only live process is a leftover Redis
    (a site add cut short) no longer reports "already running": it starts
    the bench, and the runner clears that Redis first.
  • benchbar adopt, repair and service on a bench running under
    bench start or benchfg leave its processes alone: the agent loads
    stopped and benchup takes over once that session ends.
  • benchbar site drop never drops a bench's only site, also when
    SITE_NAME or --site names another site, and treats the site in
    currentsite.txt as the default too.
  • The repair plan says when a MariaDB step restarts the server shared by
    every bench, and the restart names the running benches first.
  • The MCP tools that change a bench (benchbar_up, benchbar_down,
    benchbar_restart, benchbar_app_add) refuse a folder that is not in
    benchbar_list.
  • benchbar fg takes the CLI lock while it stops the background bench,
    so it cannot cut a running site add short, and refuses to start when
    the stop fails.
  • A fresh Homebrew MariaDB (10.4 and newer) gives root a socket login and
    no password; benchbar install read that as "root already has a
    password" and stopped with exit 2. It now secures root through the
    macOS user's socket account, as the Homebrew install leaves it, and
    continues. A server that accepts neither login still stops and asks.
  • Cancelling benchbar install at "Proceed?" (or running it without a
    terminal and without --yes) ends the run with exit 1 and "Cancelled.
    No bench or site was created"; it no longer reported the phase as done
    and loaded a launchd agent for a bench that was never created.
  • site add, site drop and the install stop with "MariaDB is not
    running" when it is not, instead of blaming the root password.
  • A stale MARIADB_ROOT_PASSWORD in the environment no longer makes the
    site step fail when the Keychain holds the working password: it is
    verified first and the Keychain is tried next, as phase 00 already did.
  • OFFLINE=1 and BENCHBAR_OFFLINE=1 reach both phase scripts; before,
    they reset it and ran the remote branch checks anyway.
  • benchbar install asks for sudo up front only for steps that will run:
    no prompt for the wkhtmltopdf package when Rosetta is missing and
    nothing can agree to install it, or when the package would be skipped.
  • The message after a stopped install says what phase 00 did ("System
    dependencies were set up as far as possible; no bench or site was
    created") instead of "Nothing else changed".
  • A command inside the run that fails with code 2 now ends the run with
    1, so exit 2 keeps meaning that phase 00 or 01 stopped for a manual
    step (most often the MariaDB root password).
  • A launchd agent that is on launchd's disabled list (after launchctl disable or an old launchctl remove) is enabled before the bootstrap
    is retried, instead of failing three times.
  • A bench whose Frappe no profile knows (develop, v17, v14) with no stored
    profile no longer falls back to v15-lts silently: doctor's and
    repair's header say the profile is only the default, the env checks say
    so and warn instead of offering a rebuild, and repair refuses
    to rebuild the env with a guessed Python. benchbar install --profile NAME sets the profile.
  • bench version failures are classified before any fix is offered: a
    missing or broken bench command gets uv tool install frappe-bench
    (or the pipx equivalent), an app that does not import is named with
    bench setup requirements --python, and only a failure inside the env
    itself (frappe or a library that does not import) still leads to an env
    rebuild. None of the classified cases moves the env aside.
  • benchbar repair refuses to rebuild the env while the bench is running
    (its processes run from that env) and says to stop it first.
  • The installer checks that bench runs (bench --version) before using
    it, names the reinstall command for its owner when it does not, and
    warns when it is older than the profile's known minimum (bench_min in
    config/release-profiles.tsv).
  • Long and timed commands (bench init, pip, yarn, git, brew) run in their
    own process group, and a timeout, a Ctrl+C or a TERM to benchbar stops
    the whole group before the run ends and the lock is released; before,
    only the top process was signalled and the rest kept writing into the
    bench. The MCP server does the same for a tool call that runs out of
    time, so a setup Redis never outlives it.
  • A long command that stops to read the terminal it does not have (a
    password prompt inside bench get-app or brew) is ended with a clear
    message instead of waiting for ever behind the spinner.
  • The run lock reclaims a stale lock by renaming it away, so several runs
    that find one at once leave exactly one holder, and a lock whose pid is
    not written yet is left alone for 5 seconds instead of being stolen.
  • Every mutating run also takes the bench's own lock
    (<bench>/.benchbar.lock), so a Homebrew CLI, the app's CLI and a git
    checkout working on one bench exclude each other.
  • benchbar up and restart release the locks once the agent is kicked,
    before the wait for the site, so other benches' commands are not refused
    for 45 seconds.
  • Writers of a state file build the new file under a private temp name and
    a per file lock, so a status poll and a repair no longer lose each
    other's keys.
  • app focus without an app name (the listing) takes no lock;
    self-update and register take it and write a run log.
  • Log files and backup folders carry the run's pid in their name
    (<date>-<time>-<pid>), so two runs in the same second never share one.
  • Every write helper fails closed: a backup, temp file, awk or rename
    that fails leaves the target untouched and the step is reported as
    failed, not done. A read only folder or a full disk no longer shows a
    green repair.
  • The shell rc block is written into the real file behind a symlinked
    ~/.zshrc (the link stays a link, the file keeps its mode), every stage
    is checked so a failed rewrite leaves the file byte-identical, and
    $ZDOTDIR/.zshrc is used when ZDOTDIR is set; the one line installer
    does the same for its PATH block.
  • benchbar repair under --yes or without a terminal no longer reports
    the "stop Homebrew redis" question as done: a question nobody can
    answer is left out of the plan and listed as optional, so a second
    repair says unchanged. A declined hosts entry is skipped, and its
    fix adds the line inside the benchbar block.
  • Large logs are rotated by copy and truncate, so the bench keeps writing
    to the live file (a rename took the writers along and left the live
    file empty), and at most three .old copies are kept.
  • An interrupted bench new-site is remembered (a marker in the bench's
    logs/.benchbar); the next run names the incomplete site and how to
    move it aside instead of taking it for created.
  • benchbar lock apply fails when a fresh clone cannot reach its pinned
    commit (it reported done), leaves that clone out of the build, and
    skips an app whose detached HEAD holds commits no branch has instead
    of orphaning them.
  • site drop moves the default site only after drop-site succeeded; a
    failed drop leaves the default where it was.
  • The runner treats honcho ending with code 0 without a stop request as a
    crash (a bench process ended, for example redis on a stray TERM), so
    launchd restarts it under the crash guard instead of leaving the bench
    silently down; and its crash guard fails closed when the start history
    or the stop flag cannot be written.
  • The legacy agent migration and uninstall-service keep the plist when
    launchd does not let go of the job, and report a move that failed.
  • A bench folder that cannot be written (read only) stops a run at once
    with "Could not create the lock ... (read only?)", instead of five
    retries that blamed another benchbar run.
  • benchbar logs --process worker (and file: worker in the MCP
    benchbar_logs_tail) always returned no lines: Procfile.lean sends the worker to
    logs/worker.log, not to bench.log. It now reads that file
    (--previous with it is refused: there is no previous worker log).
  • --json output removes a terminal color code as a whole; before, the
    escaping dropped only its ESC byte and left [31m in log lines and
    messages.
  • An MCP action whose follow up status failed or hung lost the action's
    result; the result now comes back with after_error. Invalid UTF-8 on
    stdin no longer ends the server.
  • The one line installer's state (default bench, port claims, per bench
    settings in ~/.local/share/benchbar/.benchbar) now moves to
    ~/.local/state/benchbar even when BenchBar.app made its
    bin/benchbar link there before the first CLI run; before, the new
    folder counted as moved and the old state was left behind. The move is
    serialized with an atomic guard, so first runs started at once (the
    app's polls) leave one state folder, a guard left by a killed run is
    reclaimed, and a run cut short at any step leaves a layout the next
    run completes (doctor's Second CLI check says so meanwhile).
  • The helper block (benchup and friends) written by the app's CLI names
    Homebrew's or the installer's benchbar when one hands off to it, and
    the app's link only when nothing else is installed: the link goes with
    the app, that copy keeps running on its own.
  • Doctor's Second CLI check under the app's CLI reports a second state
    folder in ~/.local/share/benchbar next to the user state (never a
    delete: the Trash is offered) and a Homebrew or installer copy newer
    than the app's CLI, with the app update as the fix.
  • benchbar where shows the version of the copy that handed off
    (handoff_from_version in the JSON) and the folder behind the state
    path when it is a link (state_dir_real, state LINK -> REAL).
  • install.sh --uninstall keeps the checkout when
    ~/.local/state/benchbar is a link into it (the state on another
    volume), and says to move the state first.
  • Docs: status, list and doctor --json are read only except for the
    one time move of the state folder, which renames and links.
  • benchbar install of a second bench while another bench or program
    holds the default ports picks the next free port block (or the one
    --port-offset names) and writes it before phase 01 starts the bench's
    Redis; it stopped with "Port 11000 ... is held by another process".
  • A fresh v15 install puts setuptools<70 into the new env, as repair
    does, so the first benchbar doctor no longer warns about
    pkg_resources.
  • Doctor's stop flag check names the process that holds the bench's ports
    after a port_conflict pause, or says the ports are free now; it
    pointed at the orphans check, which only lists the bench's own
    processes.

Changed

  • The runner (template v7) and the helper block (v2) changed; benchbar doctor marks existing ones outdated once and benchbar repair
    re-renders them.
  • repair says when a check still fails after the run and has no action of its own (a disabled formula, a missing tool); doctor exits 1 while it does.
  • The default profile v15-lts installs node@22 instead of node@20,
    which Homebrew disables on 2026-10-28 (Frappe v15 needs Node 18 or
    newer). A bench set up with node@20 keeps working; benchbar doctor
    flags its shell block, agent, Node and the missing formula, and benchbar repair installs
    node@22, puts yarn under it and re-renders both PATHs. node@20 is
    not removed.
  • benchbar install does its two sudo steps (the wkhtmltopdf package,
    the /etc/hosts line) first and drops the credential with sudo -k
    before Homebrew, pip, npm, yarn or bench get-app run; it still asks
    once, up front. 00-mac-system-deps.sh run on its own drops it right
    after the package step.
  • The wkhtmltopdf package is copied into a root owned folder under /tmp,
    hashed there as root and installed from that copy, so the file that was
    verified is the file that is installed. --dry-run prints those steps.
  • MARIADB_ROOT_PASSWORD and ADMIN_PASSWORD from the environment reach
    only benchbar and the two phase scripts; no other child process (brew,
    pip, npm, yarn, bench) sees them.
  • --yes, --make-default and an approved port plan are flags only: a
    FL_ASSUME_YES, FL_MAKE_DEFAULT or FL_PORT_PLAN_APPROVED left in
    the environment no longer answers a question. benchbar mariadb-password without a terminal says that --yes is needed instead
    of printing the password.
  • The Keychain item for the MariaDB root password is written through
    security -i (the password is never a command line argument) and names
    /usr/bin/security as its one trusted application. SECURITY.md says what
    that means and how to make every read ask.
  • benchbar report also masks secrets that have no key in front of them:
    user:password@ inside URLs (redis, git with a token), JWTs, GitHub,
    Slack, OpenAI and AWS token shapes, and email addresses (<email>);
    pwd and passwd keys and Python keyword arguments (password='...')
    count as credential keys. REDACTIONS.txt lists each kind.
  • benchbar logs --json redacts the lines with the report's rules and
    caps -n at 2000 lines, saying so with "truncated_to":2000.
    benchbar_logs_tail over MCP redacts the same way and refuses lines
    outside 1 to 2000. benchbar logs without --json is still the file as
    it is.
  • A git URL with a token in it no longer reaches the run log, the "Last
    command failed" line, a failed command's last 40 lines or the echo of
    an env-provided answer: the user:token@ part is written as ***@.
  • adopt, install, service, repair, up, restart, autostart,
    ports apply, site add and site default refuse a bench path (and a default site name) with a double quote,
    backslash, $, backtick, <, >, & or a control character before
    writing anything, and say which character is in the way; spaces and
    apostrophes stay fine. doctor stays read only and reports it as the new
    bench_path check. Template values are also escaped for bash and XML as
    a second guard.
  • The /etc/hosts rewrite (a line inside benchbar's block, or removing
    one on site drop) happens on the root side: awk as root writes
    /etc/hosts.benchbar.new through sudo tee, the result is checked
    (every line an address and names, exactly one line more or fewer) and
    moved into place; anything else leaves the file untouched. A site folder
    whose name is not a valid site name is skipped with a warning and never
    reaches sudo.
  • The state store (state.env, the per bench files) decodes its values
    without eval: a hand edited KEY=$(...) line is read as text, runs
    nothing and expands no glob.
  • The MCP server runs every tool call on its own thread, so a status
    answers while an app add runs; actions run one at a time; a
    notifications/cancelled stops the running CLI and its process group
    (a cancel during the status that follows an action still returns the
    action's result, with after_error).
    Arguments are checked against the schema and a bad value is a tool
    error that names the rule. Log lines, git output, hooks.py, profile
    files and action output come back with a first line that labels them
    as data from the bench, not instructions, with terminal colors removed
    and a 200 kB cap (BENCHBAR_MCP_MAX_BYTES). tools/list and
    tools/call before initialize are refused (-32002). Each tool
    says whether it is read only, destructive, idempotent or reads the
    network, and the app add token is described as what it is: proof that
    the bench has not changed since the plan.
  • benchbar mcp runs on /usr/bin/python3 -I when the Command Line
    Tools are installed, else python3 from PATH, and refuses anything
    older than 3.9 with the versions it found; a GUI client's PATH no
    longer picks the interpreter.

Added

  • benchbar doctor --json says where the profile came from
    (profile_source), and the app's Health page says when the profile is
    only the default for a bench no profile matches.
  • BenchBar.app knows the port_conflict stop reason: "Paused: a port is in
    use" with a link to Health, instead of "Paused after repeated crashes".
  • New doctor check env_setuptools: on Frappe v15 the env must import
    pkg_resources (setuptools 70 and later dropped it); repair installs
    setuptools<70 into the env, and an env rebuild on v15 does the same.
  • New doctor check "Formula lifecycle": a WARN when Homebrew disables one
    of the profile's Python, Node or MariaDB formulae within 90 days, a
    FAIL once it has or once brew no longer knows the formula.
    scripts/check-profile-formulae.sh runs the same
    check over every profile and stops a release in release-local.sh.
  • benchbar install --offline skips the remote checks of both phase
    scripts.
  • benchbar mcp has a read only benchbar_app_list tool (app list --json --no-sites), and benchbar_logs_tail takes file: bench,
    worker (logs/worker.log), worker_error or previous.
  • benchbar site list --json carries each site's db_name and the
    MariaDB db_port (never the password).

Signed with a Developer ID and notarized by Apple. Install with Homebrew:

brew install askysh/tap/benchbar askysh/tap/benchbar-app

or with the DMG, or with the one line installer:

curl -fsSL https://raw.githubusercontent.com/askysh/benchbar/main/install.sh | bash

Checksums are in SHA256SUMS.