CLI changelog
What changed in each release of the Pushary CLI, newest first
This is the changelog that ships inside the CLI package on npm, published as pushary and as @pushary/agent-hooks. Both names carry the same build. A change on the server or in Pushary for Mac can change what your agents see without a CLI release. Those changes are in the changelog and the Pushary for Mac release notes.
Changelog
1.9.27
- A
Bash(...)deny or ask rule you write in Pushary now catches the command it names however it is spelled. With aBash(git push:*)deny and a rule that otherwise allows Bash, these used to run and are now refused:git -C . push,git -c x=y push,git --no-pager push,git --git-dir=.git push,GIT_DIR=.git git push,FOO=1 git push, a push behindenv,nohup,time,nice,timeout 60,exec,command,builtin,xargs,sudoorarch -arm64,"git" push,\git push,/usr/bin/git push,git pushwith two spaces,(git push),{ git push; },bash -c "git push",sh -c 'git push',eval git push, a heredoc fed tobash,find . -exec git push \;, a push insideif ...; then ...; fi, and a push chained after a command Pushary could not split before, such asx=$(git rev-parse HEAD) && git push,echo hi > out.txt && git push,npm test && git push $(git remote), or Claude Code's heredoc commit followed by&& git push 2>&1 | tail -5. An ask rule now asks for every one of these. The same holds for any rule, such asBash(npm publish:*)orBash(rm:*). - A command whose program Pushary cannot see, such as
$GIT push,"$(which git)" push,bash -c "$CMD",eval "$(ssh-agent -s)"orcat script | bash, is now refused when you have a deny rule for a Bash command, and asks when you have an ask rule. Every preset includes ask rules for destructive commands, so under the Autonomous preset such a command now asks instead of running. - The read-only floor no longer approves a read that your deny rule names in another spelling. With a
Bash(cat:*)deny,"cat" .env,/bin/cat .envandc''at .envwere approved as reads. WithBash(cat .env), so werecat ./.env,cat '.env'andcat .env, and withBash(git log:*), so weregit log,"git" logandgit "log". They are now refused, and an ask rule asks for them. - A rule that approves still covers only the command as written.
Bash(git commit:*)does not start approvingenv FOO=1 git commitorbash -c "git commit". A narrow ask rule that now catches a new spelling never weakens a broader deny: with aBashdeny and aBash(npm test:*)ask,FOO=1 npm testis still refused. - The
askanddenyrules in your Claude Code settings read the same spellings. WithBashallowed andBash(git push:*)denied there, Pushary used to step aside forgit -C . pushas a call Claude Code allows. It now handles that call under your Pushary rules. pushary setupkeeps the whole pairing QR on screen for the whole wait on an 80x24 terminal. After 45 seconds the install hint, and after a minute the browser sign-in command, used to print as new lines under the code. Each one scrolled a top row of the code off the screen, so after a minute it no longer scanned. They now take turns with the minutes left on the waiting line itself. When setup's output is not a terminal, each still prints once on its own line. The install hint now says "scan the QR above" instead of "scan the code above" so it fits on one 80-column line.- The background service now tells Pushary whether automatic updates can run on this computer, and if not, why: turned off, CI, Windows, Linux, or not a global install. It also sends the result and the time of the last update check, with no file paths or error text. Your agent overview uses it to show a computer that is stuck on an old version.
- The bundled Pushary skill now gives the When I'm out window as 20 seconds by default, which is what the built-in rules use. It said 10.
1.9.26
archis no longer approved as a read. On a Mac,arch -arm64 <command>runs the command after it, soarch -arm64 git push --forcewas approved without asking, even under aBash(git push:*)deny rule. A command behindarchis now judged like the same command without it. Useuname -mto read the architecture; it is still approved as a read.- Codex in any mode except full access now asks your phone before a command that changes something through GitHub, Stripe, AWS or Twilio, such as
gh pr comment,gh api -X DELETE,gh api ... -f field=value, a GraphQL mutation,stripe refunds create,aws lambda invoke,aws ses send-email, an upload withaws s3 cporaws s3 sync, ortwilio api:core:messages:create. Since 1.9.15 these ran without a question whenever Codex did not raise one of its own, which is the case when its sandbox allows network access. Reads such asgh pr view,gh api repos/...,stripe charges list,aws s3 lsandaws ec2 describe-instancesstill run without asking. - In the same modes Codex now also asks before a patch to a
.envfile, a credentials file or a GitHub Actions workflow. Codex applies an edit inside the project without asking, so these used to change with nothing shown..env.exampleand other files under.githubare not affected.
1.9.25
- "Always allow Bash in this workspace" and "Always allow Bash in this repository" now still ask about a command with a real consequence. That is running downloaded code (
curl ... | sh, or a file downloaded and then run), a production deploy or migration (vercel --prod,RAILS_ENV=production bin/rails db:migrate), a schema push or reset (prisma db push,prisma migrate reset,supabase db push,drizzle-kit push), a SQL client that writes or runs a script file (psql -c "UPDATE ...",psql -f,psql < file,sqlite3 app.db < schema.sql),redis-cli FLUSHALL, a cluster deploy (kubectl apply,helm upgrade), publishing (docker push,npm publish),gh pr merge,gh repo delete,aws s3 sync --delete, restarting, stopping or unloading a service (systemctl restart,launchctl unload), throwing away uncommitted changes (git checkout <file>,git restore,git branch -D), and writing or deleting a.envor secrets file. These used to run without a question. They are recognised behind wrappers such assudo,env,timeoutandxargs, behind global flags such asgit -C dir, insideifandforblocks, and with output redirected to a log file. - Under those rules a plain
git push,git push -u origin HEAD,git push --dry-run,git clean -n, and anrm,git rmorunlinkof a file keep running. Asking about every push and every removal under a rule that says always allow is too much. A force push,rm -randgit clean -fstill ask, as before, and a force push now also asks when it is written asgit -C dir push --force,git push origin +main, or a push that deletes or mirrors branches on the remote (--delete,:branch,--mirror,--prune). - The same applies to a bare
Bashapprove rule written on the Policies page and to rules saved before this release. Presets and "Allow Bash for this session" keep their own rules. To let one of the held commands run anyway, add a specific rule such asBash(docker push:*). - Claude Code's commit form,
git commit -m "$(cat <<'EOF' ... EOF)", is now read as a commit when every shell reads its message as plain text, including macOS/bin/bash3.2. "Allow Bash for this session" asks about fewer commits, aBash(git commit:*)rule now covers them, and a&& git pushafter one is seen, also by aBash(git push:*)deny rule. A message with an unmatched), an apostrophe, a backslash, a backtick, or a line that starts with the closing word such asEOF, is not read this way and asks as before. - Production deploys and migrations, SQL that writes, runs a script file or comes from a variable or a pipe,
mongosh --eval, cluster deploys and schema resets (kubectl apply,helm upgrade,prisma db push),pg_restore --clean,mongorestore --drop,gh pr merge, and publishing or deploying withdocker push,twine upload,cargo publish,npm unpublish,gh release create,pulumi up,cdk deploy,firebase deploy,fly deployorwrangler deploynow also ask under "Allow Bash for this session". These checks skip a command run with--dry-runor--help, but a command that says deploy, publish or release, such ascargo publish --dry-runorfirebase deploy --dry-run, still asks, as it did before this release.
1.9.24
- Under When I'm out, a call nobody answers is refused for every agent with no approval prompt of its own (Antigravity CLI, Cursor file edits, sessions started from your phone), even when your rule approves on timeout. The 1.9.17 notes said this; only Codex did it. Under Every time a rule that approves on timeout still runs the call after its full wait.
- After three approvals nobody answered in a session, an Every time call whose rule approves on timeout keeps reaching your phone instead of running at once with nothing shown. Antigravity CLI sessions are no longer paused at all: agy sends nothing that could lift the pause, so every later call was refused until a new conversation.
- "Allow for this session" is offered for an MCP tool only when its name says it reads, such as
get_issueorlist_files. A grant cannot see the arguments of later calls, so a tool such asqueryorcreate_issueasks every time. A session grant already given for such a tool stops applying. pushary sign-out --revokeandpushary clean --everythingrevoke only a key this CLI created by browser sign-in or pairing. The Pushary Mac app's key, a key you pasted or passed with--key, and a key from anexport PUSHARY_API_KEYline are removed from this computer and not revoked, because the Mac app or another machine may still use them; the output says so and links to the dashboard to revoke it there. Since 1.9.8sign-out --revokecould revoke the Mac app's own key. A key saved before 1.9.24 has no record of how it was made, so it is removed and not revoked until your next sign-in records one.
1.9.23
- A
printfwhose format has a numeric conversion, such as%d,%xor%f, is no longer approved as a read unless every argument is a plain number. zsh, the default shell on a Mac, evaluates those arguments as arithmetic, and an argument such as'PWD[$(command)]'ran the command while Pushary approved it without asking.printf '%s\n' text,printf helloandprintf '%d\n' 42are still approved as reads. - Codex with its approval policy set to never: answering "Handle on your machine" on your phone now refuses the call. Codex has no prompt of its own there, so the call used to run, a destructive one included. With Codex's own prompts on, the call still goes to that prompt.
- Antigravity CLI: when your Pushary subscription has ended, a call Pushary would ask about is refused with a message that says so. Pushary is agy's only gate, and the call used to run with nothing asking. Renew, or run
pushary disconnect antigravityto give approvals back to agy.
1.9.22
- Each approval Claude Code or Codex sends through Pushary now carries the approval mode the agent reported for the session, such as
defaultorbypassPermissions, so Pushary can tell which mode an approval was asked in. It is recorded with the approval and never used to decide one. A mode that is empty or not text is left out, and a long one is cut to 64 characters.
1.9.21
pushary setuprecords a key as the one Pushary for Mac is signed in with only when Pushary for Mac holds that same key. It used to record any key it reused from your agent settings that way, including one left behind by an earlier CLI sign-in.pushary cleannever revokes the Mac app's key, so it skipped that leftover key and left it working. Now it revokes it, and still leaves the Mac app's own key alone.- For the same reason, a leftover CLI key is no longer reported as "signed out on this Mac" when it stops working.
- A key recorded by an earlier version stays recorded until setup saves a different key.
1.9.20
pushary setupcalls a key found in your agent settings the Pushary for Mac key only when Pushary for Mac holds that same key. A key left behind by an earlier CLI sign-in is called a key found in your agent settings, also when Pushary for Mac is installed.- On Windows, when npm cannot replace a file during the global install (EPERM), setup says to close other terminals or editors that use Node, then run setup again. It used to suggest an npm prefix change, which does not help when another program has the file open. EACCES, and EPERM on macOS and Linux, keep the npm prefix advice.
- The pairing screen prints the fingerprint directly under the QR code, above the waiting line, so the QR code, the fingerprint and the waiting line all fit an 80x24 terminal. The fingerprint used to sit above the QR code and scroll off.
Wrong account? Run npx pushary@latest setup --connect appis printed only where that command can pair: not in CI, and not with--skip-phoneor--connect none. On an inactive plan it is printed once, next to the account with no plan, instead of twice.
1.9.19
- When nobody has answered the last 3 approvals in one agent session, Pushary stops sending that session's approvals to your phone. Each further call ends at once, exactly as it would have if nobody had answered it for the whole wait: under When I'm out Claude Code asks in its own prompt, a rule that refuses on timeout refuses it, a rule that approves on timeout runs it at once with nobody asked, and Codex set to never ask for approval refuses it. Nothing is sent, and the paused approval does not appear on your phone or in the Mac app. A command that deletes, rewrites history or reaches the network, such as
git push,rmordropdb, and any call Pushary flags as destructive, is still sent to your phone. - Approvals go back to your phone as soon as you answer any approval in that session, send it a new prompt, or approve at your agent's own prompt a call Pushary handed there. That last one works for Claude Code and for Codex calls checked before they run, whose completion Pushary sees.
- An approval counts as unanswered only once its wait has ended with nobody answering, so several approvals open at the same time never pause the next one. Approvals you may have answered at your terminal without Pushary seeing it, in Gemini CLI, OpenCode, Cursor's shell and MCP approvals and Codex's own approval prompt, never count, so those sessions are never paused.
- Earlier CLI versions are paused only for Claude Code, and only where the call would have ended in Claude Code's prompt anyway. In a session started from your phone, CLI 1.9.6 to 1.9.12 refuse a paused call with their message that the remote session has no local approval UI. The Pushary Mac app keeps sending every approval to your phone for now.
1.9.18
Notes corrected on October 4, 2026. The first version described only the Cursor case and left out the setup and skill text changes.
pushary disconnectleaves a config file it cannot read exactly as it is and lists it as untouched, for every agent. For Cursor it also keeps the plugin folder when the hooks file that points into it cannot be read, so Cursor's gate never points at a script that was deleted.- After setup, the line on how to test reads "Run a command that needs approval. Away from this computer, it reaches your phone." The bundled Pushary skill now says When I'm out waits 10 seconds for your phone by default, not 45.
1.9.17
Notes corrected on October 4, 2026. The first version said a rule that approves on timeout lets the call run under When I'm out. It does not: the call is refused.
- When Codex runs with its approval policy set to never (full access), every call Pushary asks about goes to your phone, also while you are at the keyboard. Codex has no prompt of its own to ask you there. The wait is your policy's timeout, also under When I'm out.
- If nobody answers, the call is refused with "No response within timeout". Before, the command ran once the wait ended, a destructive one too, and at the keyboard it ran without anyone being asked. A wait cut short by Codex's hook time limit, and a question no phone could receive, are refused the same way.
- In Every time, a rule's timeout action still applies after its full wait: approve on timeout lets the call run, and deny on timeout refuses it. Under When I'm out, an unanswered call is refused whatever the rule's timeout action says.
- In Codex's other approval modes nothing changes. The call goes back to Codex, which asks in its own prompt when the call needs it.
1.9.16
pushary setupnames the account before it reuses a key: the key saved on this machine,PUSHARY_API_KEY, or a key found in your agent settings. At a terminal it asksContinue as @workspace? (Y/n), and No connects a new key the way you asked: the Pushary app on your phone by default, or a browser sign-in with--connect browser. Without a terminal it names the account and printsWrong account? Run npx pushary@latest setup --connect app. A key found in your agent settings is called the Pushary for Mac key only when Pushary for Mac is installed. A key on an inactive plan names its account and the same--connect appline instead of telling you to re-run setup with the saved key.- The pairing screen prints the QR code last, directly above the waiting line, so it no longer scrolls off a 24-row terminal. Above it: what to scan, the short link, the fingerprint in plain words, the app link, and where to get the app. The browser fallback is offered once, after a minute.
- Stopping setup with SIGTERM while it waits for the phone cancels the pairing, as Ctrl-C already did. An agent whose tool call times out no longer leaves a live QR behind.
- Setup saves a paired key as soon as the key is checked, before it asks which agents to set up. Stopping at the agent list keeps the key, and the next run does not draw a new QR.
- When the phone answers the test question, setup ends with
You're set. Your phone answered in 3s.and one line asking you to restart open agent sessions. The two "Agent hook activation remains unverified" lines are gone. A test nobody answered now shows! No answer yetinstead of a tick. - Setup says
Signed in to @workspace (Agent plan)instead ofKey verified (workspace · plan agent), and names the agents Pushary for Mac handles in plain words. - The transcript notice is one line: transcripts sync redacted and encrypted, your phone can decrypt them, authorized Pushary compliance admins can recover them for documented legal obligations, and the command that turns sync off. Transcript sync is still on by default.
- The two questions after setup wires your agents say plainly what they change: a Pushary section in this folder's
CLAUDE.mdorAGENTS.md, and aclaudealias in your shell profile. Both still default to No. - On Windows every printed
npx pushary@latestcommand readsnpx.cmd pushary@latest, which PowerShell runs without the execution policy blocking it. - When npm cannot install globally (EACCES or EPERM), setup says to use nvm or set an npm prefix, instead of offering a retry that fails the same way.
- Setup prints one line before it installs the phone-start background service, with
--control offto turn it off. - When Pushary for Mac is signed into a different account, setup names both accounts and the two ways out: sign Pushary for Mac into this setup's account, or take over the hooks with
--take-over-hooks. - An unreachable phone is pointed at
npx pushary@latest setup --connect appto pair the Pushary app, not at web push. - On an unsupported Node,
pusharysays which releases it needs, which one you have, and where to get Node.js.
1.9.15
Notes corrected on October 4, 2026. "Allow for this session" for an MCP tool also reaches Claude Code questions; the first version said Codex only.
- Codex in Every time asks your phone only about the calls that matter. Codex raises its own approval when a command leaves its sandbox, a forced
rmruns, an MCP tool is not marked read-only, or a patch lands outside the project, and Pushary answers that request in your mode as before. Before a call runs, Pushary now adds a question only when the call leaves the scope you agreed, a rule you wrote, a preset you picked or an approval route covers it, the command destroys, rewrites history or reaches the network, or a patch deletes a file. Commands that only build, test or read no longer reach your phone, and an MCP tool Codex asks about itself is asked once, through Codex's own request. The kill switch and your deny rules still apply to every call. - In Every time with full access, where Codex asks nothing itself, every call your policy gates still comes to your phone, as before.
- When I'm out is unchanged for Codex, except for scope: Codex's own approval requests go to your phone first, and nothing else is asked before a call runs.
- A Codex edit outside the scope you agreed is now asked in Every time and When I'm out, also while you are at the keyboard, because Codex has no prompt for it there. Yes lets it run. No refuses it. No answer before your policy's wait ends refuses it with the path named. In Updates and Terminal, where nobody would be asked, it is refused. A patch that leaves the scope across several files is refused with a request to split it into single-file edits, as with Claude Code. Before this release Codex applied these edits without asking.
- In Updates, Pushary sends an update before a Codex call only for the calls above. Codex's own approval requests still send one each.
- A patch whose
*** Delete File:or*** Update File:header is indented, which Codex applies, is now read as the delete or edit it is, so your path rules and the delete question apply to it. - A command that deletes through a cloud CLI subcommand spelled as one word, such as
aws dynamodb delete-table,aws ec2 terminate-instances,heroku apps:destroyorfirebase firestore:delete, now counts as destructive for every agent. Pushary asks about it instead of leaving it to the agent's own allowlist, and an "allow Bash for this session" grant does not cover it. - Codex questions can offer "Allow for this session" for Bash, where risky commands still ask, as Claude Code questions already did.
- After three approvals in a row for one exact MCP tool, the question can offer "Allow for this session" for that tool, in Claude Code and in Codex. It is never offered for Pushary's own tools or for a tool whose name says it runs code, sends, merges, publishes, deploys, writes, deletes or moves money.
1.9.14
- Antigravity CLI approvals go to your phone every time. agy has no approval prompt of its own while Pushary is its only gate, so under When I'm out the phone is no longer skipped because you are at the keyboard, and the wait is your policy's timeout instead of the short push window. Before, such a call was blocked with nobody asked, or after 10 seconds when you were away. Other agents keep When I'm out as it was.
1.9.13
Notes corrected on October 4, 2026. The last two bullets, the setup receipt and the Windows exception were missing when 1.9.13 shipped.
pushary setupmakes Pushary the only gate for Antigravity CLI. When~/.gemini/antigravity-cli/settings.jsonhas notoolPermission, setup sets it toalways-proceed, so plainagyno longer asks in its own prompt and approvals from your phone take effect. A mode you chose yourself, such asstrict, is left alone, and setup says agy will still ask in its own prompt. Setup records the change in~/.pushary/setup/antigravity-tool-permission.json.pushary disconnect antigravityandpushary cleanputtoolPermissionback, but only if Pushary set it and only once no Pushary hook is left in~/.gemini/config/hooks.json. A value you set or changed is never touched.- An Antigravity CLI call that Pushary cannot decide is now blocked with a reason instead of running unasked: when Pushary is signed out, cannot be reached, or cannot establish its policy. Other agents are unchanged and still fall back to their own prompts.
- If the Pushary CLI is uninstalled while agy is still connected, agy's approval hook blocks each call and says how to undo the setup, instead of exiting silently and letting the call run. On Windows the hook still exits silently in that case, and agy runs the call.
- A session started from your phone asks your phone every time under When I'm out, also when this computer looks attended, and waits for your policy's timeout. Before, when another session had prompted in the last five minutes or Pushary for Mac was attended, the phone was skipped and the call was denied with "no local approval UI". Nobody was asked.
- The Cursor plugin setup installs asks your phone about every file change under When I'm out, also while you are at the keyboard, and waits for your policy's timeout. Before, the edit was denied with "Switch to Every time" and nobody was asked, or it was denied after the 10 second push window when you were away. Shell commands and MCP calls in Cursor keep When I'm out as it was, because Cursor can prompt for those itself.
1.9.12
Notes corrected on October 4, 2026. 1.9.12 shipped with no notes. The bullets below were added in the 1.9.13 and 1.9.15 packages, and the error text and the refused options in full were added on October 4.
- An Antigravity CLI tool call that failed is recorded as failed in the session, where it used to show as completed. The session now shows the call's error message: up to its first 500 characters, with secrets redacted, as for other agents.
pushary disconnect fxis recorded in~/.pushary/declined-agents.json, so Pushary for Mac versions that read that file leave fx off until you connect it again.rg,sort,uniq,sleep,pgrepandgit branch --show-currentrun without asking when they only read. This applies to every agent.rgstill asks with an option that runs another program (--pre,--pre-glob,--hostname-bin,--search-zip,-z).sortstill asks with an option that writes a file or runs a program (-o,--output,-T,--temporary-directory,--compress-program).uniqstill asks with a second file, which it writes.- Codex's
wait_threadsandsend_message_to_threadrun without asking, matched by exact name. The kill switch, a deny rule and a rule you wrote for the tool still win over them. - A deny rule now blocks read-only commands too. A bare
Bashdeny, a*deny or a preset that never runs the shell used to letlsand other reads through.
1.9.11
pushary setupconnects Antigravity CLI (agy). Pick it in setup or pass--agents antigravity_cli. Setup adds a hook namedpusharyto~/.gemini/config/hooks.jsonforPreToolUse,PostToolUseandStop, and the Pushary MCP server to~/.gemini/config/mcp_config.json. Before agy runs a shell command, writes or edits a file, or calls an MCP tool, your policy decides, and the risky ones wait for your answer on your phone or your Mac. Pushary answers agy only with allow or deny: a denial, or no answer before your policy's wait ends, blocks the call with a reason the model sees.- Approvals for Antigravity CLI take effect only when agy runs with
--dangerously-skip-permissionsor with"toolPermission": "always-proceed"in~/.gemini/antigravity-cli/settings.json, because agy ignores a hook's allow in its other modes. Setup prints this and does not change agy's mode. Pushary is then the only gate: if the hook cannot reach Pushary, agy runs the call. - Setup skips Antigravity CLI when
agyis not installed, even when~/.geminiexists for Gemini CLI. - While agy waits for your answer, its terminal shows "If nobody answers, it does not run." instead of saying the terminal takes over, which agy has no way to do.
pushary disconnect antigravityremoves thepusharyhook and the Pushary MCP server and leaves your other hooks and servers alone.
1.9.10
pushary setupno longer reports Qwen Code, CodeBuddy, Qoder, Droid and Devin as an account mismatch when Pushary for Mac connected them and the CLI's key is in the same account. Setup compares the account of the key in their MCP entry for Qwen Code, CodeBuddy and Qoder, and the account of the Mac app's own key for Droid and Devin, whose hooks run through the app. They are kept and listed as kept for the Mac app. If the app is in a different account, setup reports it and changes nothing. The CLI does not copy or save the app's key.pushary doctorno longer fails "Global package installed" on a computer Pushary for Mac connects, for example when run with npx. The app runs the hooks there, so doctor prints that the global package is not installed and not needed. It still fails when the CLI has its own key or owns any agent's hooks, because those hooks run the global package.pushary clean --everythingrevokes the keys the CLI wrote on pushary.com before removing them: the key in~/.pushary/config.json, and a different key in theexport PUSHARY_API_KEY=...line setup writes to your shell profile. It says whether each key was revoked. A key that is only in thePUSHARY_API_KEYenvironment variable is left alone and named, because Pushary did not write it and it may be a server key you use elsewhere. If pushary.com cannot be reached, the clean still finishes and tells you where to revoke the key.--dry-runrevokes nothing and lists the keys it would revoke. The Mac app's key is never revoked, and a plainclean,disconnectandsign-outwithout--revokebehave as before.pushary setup --take-over-hookstakes over Droid's hooks from Pushary for Mac. It used to fail with "no Mac app wiring was found", because Droid keeps its hooks at the top of~/.factory/hooks.json.
1.9.9
- The VS Code hooks setup writes now run the gate through the same shell guard as every other agent. Before, a missing Node or a deleted plugin folder made the VS Code hooks fail on every tool call; now they stay silent and VS Code carries on as if Pushary were not installed. Run setup again for VS Code to get the guarded hooks.
pushary doctor,pushary statusandpushary pairrecognise a computer Pushary for Mac connects when the CLI has no key of its own. Doctor and status say the computer is connected through Pushary for Mac instead of reporting a missing key, and pair tells you to pair a phone from the app's Settings > Phone. The CLI does not use, copy or save the app's key.- A default
pushary setupturns on phone-start when~/.pushary/config.jsonexists but has nocontrolMode, aspushary loginand the Mac app's Hermes connection leave it. Before, setup skipped phone-start on those machines.--control off, or a savedcontrolModeofoff, still keeps it off, and upgrades still never add the background service on their own. pushary setupkeeps an OpenCode plugin Pushary for Mac installed, as it already did for the other agents, and lists it as kept. It used to overwrite the app's plugin, MCP key and instructions. Pass--take-over-hooksif you want the CLI to own it. If the app is signed into a different account, setup reports that for OpenCode and changes nothing.
1.9.8
Notes corrected on October 4, 2026. The first three bullets were missing when 1.9.8 shipped.
- Phone pairing is now
pushary pairand signing out ispushary sign-out.pushary connectandpushary logoutstill work as aliases, with the same flags. Setup, status, doctor andpushary bellsay "pair a phone". pushary disconnect fxremoves what setup added for fx: the Pushary server in~/.fx/mcp.json, the permission rules setup recorded, that record, and the Pushary block inAGENTS.md. Your own entries are left alone. With nothing connected it says fx was not connected and changes nothing.pushary modeacceptsupdates-onlyandupdates_onlyfor Updates (notify_only). Setup and disconnect accept both agent id forms, such asclaude_codeorclaude, and reject an unknown id with the list of valid ones.pushary upgradeand automatic updates no longer take over agents Pushary for Mac connected. They used to replace the app's hooks with the CLI's and overwrite its MCP key for Claude Code, Codex, Gemini CLI, VS Code and OpenCode. An agent the app owns is now left exactly as the app wrote it and listed as kept for the Mac app, using the same rule setup uses. Pass--take-over-hooksto setup if you want the CLI to own it.pushary cleankeeps the Mac app's wiring for Qwen Code, CodeBuddy, Qoder, Droid and Devin while the app is installed, as it already did for the other agents.clean --everythingstill removes it.pushary disconnect cursorremoves the Cursor plugin setup installed in~/.cursor/plugins/local/pushary, including themcp.jsonthat held your API key, and setup's leftover copies of it. The plugin is kept, and the output says so, when the gate in~/.cursor/hooks.jsonthat points into it could not be removed.pushary disconnectremoves the Pushary block it wrote toAGENTS.md(Codex, OpenCode),GEMINI.md,QWEN.mdandCODEBUDDY.md. Only the text between the Pushary markers is removed. Skills are left in place.pushary disconnect <agent>is no longer undone by the Mac app. It records the agent in~/.pushary/declined-agents.json, and Pushary for Mac versions that read that file leave the agent off until you connect it again from the app or withpushary setup.pushary mode,mode clear,wait <seconds>andsuggestionsno longer say "This key was rejected" when the key is fine. Keys frompushary login, phone pairing and the Mac app cannot change approval settings, and these commands now say so, point you to the Pushary app, your phone or the dashboard, and exit with code 2.pushary sign-out --revokerevokes those keys too, and then checks with pushary.com and tells you whether the key still works. Before, it could not revoke them and always said the key was still valid.--jsonaddskeyStillValid.- When the Mac app signs out of the key setup borrowed from it, the background service stops once instead of restarting every 10 seconds, and
pushary doctor,pushary statusand commands that report a rejected key say the key was signed out on this Mac, tell you to runpushary login, and name the shell profile line that still exports the old key. Pushary does not edit that line. A background service with no key at all also stops instead of restarting.
1.9.7
pushary claudeand thepushary daemoncommands wait at most 5 seconds for the background service again. Only setup and upgrade allow the 30 seconds a cold Windows start can need, so launching Claude no longer sits silent while the service starts.- An automatic update whose configuration refresh fails now retries after 5 minutes, then after an hour, then once a day, instead of every 5 minutes. On a Mac with phone-start on, each retry restarted the background service.
- A refresh that cannot succeed until you change something (a legacy phone-start service, phone-start turned on without a usable agent, or a config file Pushary cannot rewrite) is no longer retried automatically.
pushary doctorshows it, and runningpushary upgradeafter the fix finishes it. - When a Codex approval hook is turned off in Codex, setup and upgrade tell you to enable it in
/hooksinstead of asking you to trust it. A missing or background (asynchronous) approval hook points you to setup again, as doctor does. - Upgrades, including automatic ones, regenerate the OpenCode plugin Pushary installed, so the 1.9.4 fix, which makes OpenCode approvals use the directory OpenCode is working in, reaches existing installs without running setup again. A
pushary.jsyou wrote yourself is never touched, and no plugin is added where there was none.
1.9.6
- Read-only shell approval no longer covers commands that make the shell evaluate quoted text again: arithmetic expansion (
$[ ]), subscripted parameters ($name[ ], including zsh flag prefixes) andprintf -v. These ran a quoted command substitution in bash or zsh while being approved without asking. They now go to your normal approval. - An escaped
;,&or|no longer splits a command when rating its risk, so destructive words passed as arguments tosh -ckeep the command destructive and above a standing Bash auto-approve.
1.9.5
- Setup allows up to 30 seconds each for daemon startup and heartbeat/control proof, allowing cold Windows agent discovery to finish before reporting a failure. Healthy services return immediately.
- Failed phone-start checks show their available local error and report which stage failed, so installation, service startup and server readiness failures can be distinguished.
1.9.4
- OpenCode approvals use the active directory for project names, policy and relative targets, including nested Git sessions and non-Git workspaces. Run setup again after updating to regenerate an existing plugin.
1.9.3
- Shell approval checks preserve non-shell Unicode whitespace as part of the executable name, so an unfamiliar command cannot inherit a read-only command’s approval exemption. Shared command analysis keeps risk classification and human approval policy aligned.
1.9.2
- Setup, doctor and login finish writing their output before exiting, including when output is piped. Windows setup saves the key without printing it when no shell profile is available.
- Editor approvals stop waiting when a question is missing or withdrawn. Pending configuration refreshes retry after five minutes, readiness tolerates client clock skew, and activation checks follow symlinks.
- Codex coverage requires enabled, trusted synchronous permission hooks. Native permission requests show their redacted input without treating it as a shell command or file edit.
1.9.1
- Fewer approvals for commands that only read. A chain of reads joined by
;or||, a|,\or{inside quotes (as ingrep "a\|b" src/orgrep '^{' file),sed -n 50,66p fileanddate '+%H:%M:%S'no longer wait for you. Before, any of these sent a prompt to your phone or the Mac notch. - A read is no longer treated as destructive because of a word it reads.
tail -5 release.log,grep "DELETE FROM" src/db.tsandcat deploy.logwere rated destructive because ofrelease,deleteordeploy, and a destructive rating overrides your own "auto-approve Bash" rule. They are now rated by what they do. A command that can act is rated as before, including a read piped into a shell or a database client, such asecho "rm -rf ~" | sh. git,find,sedanddatenow ask when an argument is something the shell expands, such as$VARor a glob at the start of a word. An expansion like that can turn into a flag the check never saw, for example a file named-deletematched byfind . -name *.- Setup's global install now takes the same lock as
pushary upgradeand automatic updates, so it never installs over an upgrade that is already running. (#1837)
1.9.0
- When you scan setup's QR code with the Pushary app, the app now shows which computer is asking to connect: its name, whether it is a Mac, Windows or Linux computer, and the agents setup found on it. Before, it showed only the fingerprint. This needs Pushary app 1.0.31 or later; older apps work as before.
- Denying the request in the Pushary app now stops setup at once with "You denied this computer on your phone". Before, setup kept waiting until the code expired.
- Setup's report to your workspace now says which kind of computer it ran on, so the Pushary app can show it next to the computer.
1.8.3
- Checking installed agents no longer blocks the daemon while npm or PATH lookups run. Concurrent refreshes share one lookup, and stopping the daemon cancels discovery.
- Automatic and manual upgrades share an operating-system lock. The finishing process keeps it if its parent exits, and a crash releases it without leaving a recovery file that blocks later upgrades. Unavailable locks stop the upgrade.
1.8.2
- Starting Codex from your phone no longer picks an old, broken Codex over the one you use. The daemon looked for agents in npm's global folder before the ones your shell runs, so a broken npm copy of Codex won over a working one elsewhere on your PATH, and the session died at once with
spawn ... ENOENT. On macOS and Linux the daemon and both session bridges now start the agent your shell finds first. Windows keeps npm's.cmdlauncher first, because the first match on PATH there can be a script Windows cannot run. - A Codex install with no native program left in its package, as XProtect left some npm installs, is no longer offered to your phone, and a request for it fails with the path it found and "Reinstall Codex". This is read from the disk; Pushary never runs the agent to check it. Setup,
pushary doctorandpushary upgradestill treat that Codex as installed, as before, so they never stop the background service because of it, and they check the service against the agents it can actually start, so a broken Codex does not make them report that the service failed. - The daemon checks every minute which agents it can start, so installing or repairing an agent shows on your phone without restarting the daemon. The daemon log says when that list changes.
- The daemon log now records how each session started from your phone ended: finished, stopped from Pushary, interrupted, stopped by a signal, or exited with its code, and after how long. Only an exit with an error code is recorded as the daemon's last error, which
pushary daemon statusandpushary doctorshow. Stopping a session from your phone, or the kill switch halting it, is not an error. - The Codex install check that setup also uses no longer judges a standalone Codex by an unrelated npm copy in a folder above it.
1.8.1
- The CLI is now also published as
pushary, from the same build as@pushary/agent-hooks, and the commands it prints now shownpx pushary@latest.npx @pushary/agent-hooks@latestkeeps working.
1.8.0
- An approval you give after auto mode blocks a call is no longer lost when the agent rewords the call. Your approval covers only the exact call that was blocked. Claude Code tells the agent only that it may retry, and an agent that sent a changed command was blocked again, so the approval you gave from your phone or the Mac notch did nothing. Pushary now refuses the first changed call and tells the agent to send the blocked call again unchanged. If the agent ends its turn without sending it, Pushary tells it to send it.
- Claude Code no longer tells your phone and the Mac notch that it is waiting for you while its background tasks are still running. Claude Code reports itself idle after a minute at the prompt even when it will continue by itself once those tasks finish. A question from the agent still reaches you as before.
- A prompt longer than 500 characters, or a shell command longer than about 490, no longer stops your agent at its next phone approval. The hook sent the whole text and the server refused it, so Claude Code halted with "Pushary could not create a verifiable approval" and Codex and OpenCode denied the call. The question, intent, action, blocker and agent name are now cut to what the card can show, and a cut line ends in "…". The server now does the same for every older CLI, so this fix does not wait for you to update. (#1604)
- A line cut to fit, including a long diff, never ends on half an emoji. The server stores these lines as JSON, and Postgres refuses a half character, so the approval failed to save.
pushary doctor,pushary upgrade,pushary cleanand automatic updates recognise a global install under either package name,@pushary/agent-hooksorpushary. A machine keeps the name it already has, and setup on a new machine installs the name it was run from. Nothing changes for an existing install.pushary doctorflags a machine with both names installed globally, because uninstalling either one removes every Pushary command.pushary upgradeno longer installs over an automatic update that is still running. It says another upgrade is running, with its process id, and stops.
1.7.3
- Approval text never hides part of a command. The value after a credential name is now hidden only up to the first space, quote or piece of shell syntax. 1.7.2 could still hide a command:
eval TOKEN="x; rm -rf ~"readeval TOKEN="[redacted]",password= rebootreadpassword= [redacted], andrm -rf {password=x,/}hid the,/that also deletes/. A private key block is hidden only when it holds nothing but the key. Tokens, keys and quoted values without spaces are hidden as before. A quoted value with spaces now shows what follows its first word, as in--password='[redacted] pass'. Logs and error reports still hide the whole value.
1.7.2
- Approval text shows the whole command again. A credential name that closes a quoted string, as in
grep -rl "password=" . | xargs rm -f, made 1.7.1 redact everything after it, so the approval readgrep -rl "password="[redacted]"and hid therm. The value after a credential name is still redacted, and the rest of the command is shown. - Hermes works again after an uninstall. When setup found Pushary's own Hermes settings already in
~/.hermes/config.yaml, left by an earlier install, its record of your config included them, andpushary cleanput them back: approvals stayed pointed at the plugin clean had just uninstalled, and Hermes'clarifytool stayed off, so Hermes could not ask you anything in the terminal. Clean now recognises Pushary's own values in that record, including records older versions wrote, and removestransport: pushary, thepusharyplugin entry, and theclarifyentry that came with Pushary's transport, while restoring everything else you set. Running clean again, or after the Mac app has disconnected Hermes, no longer fails once Hermes' defaults fill the removed settings back in. pushary cleansays what it did to Hermes: "Pushary settings removed" only when it changedconfig.yaml, "no Pushary settings" when there was nothing of ours, and it skips the plugin uninstall when the plugin is not installed.--dry-runpreviews the exact change without saving it.pushary doctorchecks Hermes when Pushary set it up: that the plugin is installed in Hermes' Python and enabled, and whether approvals and questions go through Pushary. A plugin the Mac app left installed but disabled is not reported as a failure.- Setup on a machine without npm now says so and asks you to install Node.js, instead of printing
/bin/sh: npm: command not foundand suggesting annpxretry that needs npm too. Pushary still needs npm; bun, pnpm and yarn installs are not supported yet. pushary cleantells an npm it cannot find, a package that is not installed and a failed uninstall apart, instead of calling each of them "not installed". A failed uninstall no longer ends in "Clean complete".pushary clean --dry-runlists only the Mac keychain items and preference domains that exist, and the fx setup record only when there is one. It used to list every candidate it would try. A keychain that cannot be read, for example while locked, still counts as possibly holding Pushary's items, so clean tries them and reports a failure instead of calling the keychain clean.pushary cleanhonoursNO_COLOR.- Setup's agent picker shows only the names you picked once you confirm, instead of repeating every agent's description, and the transcript notice is indented like the lines around it.
- A message you send from your phone or the Mac notch while Claude Code is running a subagent now waits for Claude Code itself. It used to be handed to the subagent's next tool call, where the main conversation never saw it.
- When your Pushary workspace has no active subscription, Pushary now steps aside and your agent's own approval prompt decides, as it does when Pushary is not installed. Claude Code, Codex, Gemini CLI and OpenCode used to refuse gated tool calls instead, telling you to retry or that the approval was cancelled, and Claude Code questions, plan approvals and MCP forms stopped the same way. A kill switch and your deny rules still apply.
- Your agent's terminal says once per session why Pushary stepped aside, with the link to subscribe.
- A Claude Code or Gemini CLI session started from your phone has no prompt on your machine, so it still refuses the call, now saying the workspace has no active subscription instead of asking you to retry.
- Pairing your phone during setup now says when your workspace has no active subscription, and to subscribe in the Pushary app or at pushary.com before running setup again. It used to say only that pairing failed.
1.7.1
- fx asks through Pushary. Setup now adds
"ask_user_question": "deny"as the last rule in~/.fx/settings.json, which removes fx's own terminal question tool from what its agent can call, so every question goes throughmcp_pushary_ask_user: to the Mac notch at the Mac, to the phone away from it. fx cannot forward its own prompt, so a question asked there used to wait unseen once you walked away. A rule you already set forask_user_questionis kept, and setup names it. The instructions in~/.fx/AGENTS.mdnow tell fx's agent to loadmcp_pushary_ask_userwithmcp_select_toolby exact name and to ask there even while you are at the terminal, and to ask in its reply only when Pushary hands the question back or cannot be reached. - fx's full-access mode (
yolo,full-accessorfull access, from settings, a workspace, orFX_PERMISSION_MODE) ignores permission rules, so the terminal question tool stays on there; setup andpushary doctorsay so instead of claiming questions are routed, and the instructions still steer the agent to Pushary. fx workspaces with their own permission rules replace the global ones, and setup and doctor name how many keep the question tool on. Setup reads the rules the way fx does (globs, last match wins, per-target maps) and leaves asettings.jsonfx would reject, such as one with an unknown action or permission mode, untouched and says fx ignores it. pushary cleanremoves the question rule only when setup recorded adding it and it is stilldeny, so a rule of your own survives.pushary cleannow removes Pushary entries from every event in~/.cursor/hooks.json, not only the events the permission gate uses. The Mac app registers its bridge on session, compaction and stop events too, and those eight entries were left behind under "Clean complete". Other tools' hooks are untouched.pushary cleannow removes the Pushary block from OpenCode's~/.config/opencode/AGENTS.md, as it already did for Codex and Gemini. Setup wrote it and clean left it.pushary cleanremoves temp files that an interrupted config write left next to an agent's config (.<file>.pushary-<id>and.<pid>-<time>.pushary-tmp), only next to the agent configs clean rewrites (your home folder, Claude Code, Codex, Gemini, Cursor, OpenCode, fx and VS Code settings), never through a symlink, and only once they are over a minute old.- Setup's summary no longer says "(others completed successfully)" or "Other agents will continue" when the failed agent was the only one; it names the agents that were configured.
- Setup's progress lines no longer leave the tail of the previous frame behind ("with Codexex", "tap the notificationon"): setup uses the shared spinner, which clears the line, keeps each frame to one terminal row, and does not animate into a log when colour is forced on.
1.7.0
- fx.
npx @pushary/agent-hooks@latest setup --agents fx, or ticking fx in the picker, connects Vercel Labs' fx coding agent. Setup adds apusharyserver to~/.fx/mcp.jsonthat runs the newpushary-mcpcommand, a local bridge that signs in with the key setup saved in~/.pushary. No key is written to fx's files, and a rotated key is used without running setup again. fx has no hooks other tools can register, so Pushary cannot answer fx's own approval prompts; fx's agent asks through Pushary's tools when it chooses to, guided by a managed block setup writes to~/.fx/AGENTS.md. - Setup adds
"mcp_pushary_*": "allow"to thepermissionrules in~/.fx/settings.json, after your own rules, so fx runs Pushary's tools without reviewing or prompting for each call, the same as the other agents' auto-allowed Pushary tools. A rule you already set formcp_pushary_*is kept. fx workspaces that set their own permission rules replace the global ones, and setup names how many there are. - Setup leaves
~/.fx/mcp.jsonand~/.fx/settings.jsonuntouched when fx itself would reject or refuse to edit them (comments, a trailing comma, a repeated key, or servers under a key fx ignores) and prints the command that retries only fx. It writes both files as compact JSON, the way fx does, and leaves a file alone rather than grow it past the size fx reads. It skips fx on a machine with no~/.fx, and on Windows, which fx does not support. fx is ticked automatically only when fx's own files are there, since other tools use~/.fxtoo. pushary doctorchecks the fx entry and that its bridge is installed, and warns when Pushary's tools are not auto-allowed.pushary cleanremoves only the Pushary server, the allow rule it added, and the instructions block.pushary setup --helplists every agent id setup accepts.
1.6.4
- Requests to Pushary now say which client made them: the CLI version, the platform, a per-process run id, and the machine id this machine already has. They go only to Pushary's own address, never to npm or any other host, and never include file paths, keys or content. Support can then find a problem from your account instead of asking for terminal output.
- When setup, a hook or the bridge cannot reach Pushary, cannot claim a blocked action, or stands aside for an agent it does not recognise, it now tells Pushary with a short code such as
mode_fetch_failed. Each code is sent at most once per clock minute per machine, not per hook, even when several hooks fail at the same moment, so an outage does not add a request to every tool call.~/.pushary/client-reports/holds one small claim file per code per minute. A report gives up after one second. SetPUSHARY_DISABLE_CLIENT_REPORTS=1to send none.
1.6.3
- When setup cannot configure one of the agents you picked, the final summary now names that agent, says why, and prints the one command that retries only it, such as
npx @pushary/agent-hooks@latest setup --agents cursor. Before, the reason was printed once in the middle of the run and the summary only said "Setup finished with 1 problem". - A config file setup refuses to rewrite, such as a
~/.cursor/hooks.jsonor~/.claude/settings.jsonwith comments or a trailing comma, is still left untouched and backed up next to the original. Fix or remove it, then run the retry command; it reuses your saved key and does not pair your phone again. setup --jsonaddsagentFailures: one entry per agent that failed, with its reason.- Setup's report to your own Pushary workspace now says which agent failed and why (the agent and a reason code only, never a file path or file contents), which check decided the exit code, and the CLI version. Support can then see what went wrong without asking you to paste terminal output.
1.6.2
- "Allow Bash for this session" now still asks before rsync deletes files:
--del,--delete-delay,--delete-missing-args,--remove-sent-files, and a long option shortened to a unique prefix such as--delete-dela. It also still asks forgit checkout -f,git checkout <ref> -- <path>,git switchwith-f,--forceor--discard-changes, branch resets (git checkout -B,git switch -C,git branch -f,-D,-M,-C), andredis-cli flushallorflushdbin any letter case, wherever the flag or word appears. Before, the grant ran these without asking.git checkout HEAD file.ts, which overwrites a file without a--, still runs under the grant. - The same commands now ask even when your own Claude Code allow rules cover them, such as
Bash(git:*)allowinggit checkout origin/main -- package-lock.json. Pushary still leaves every other command those rules allow to Claude Code.
1.6.1
- Pushary-launched Codex sessions can answer supported questions from inside MCP tools through Pushary. Invalid answers and cancelled requests never become approval. This does not connect existing Codex Desktop Computer Use permission dialogs.
- Claude forms with rules Pushary cannot preserve now stay in the native client instead of silently losing those rules.
1.6.0
- Pushary no longer asks about a Claude Code command your own Claude Code allow rules already cover when only a Pushary default would have asked. Risky commands still ask: destructive, network and data-loss commands, and anything whose effect cannot be read from its text, such as
bash -c, inline interpreter code,xargsor a#comment. If Claude Code prompts anyway, the approval still reaches your phone. - Your Claude Code rules are read from your managed, user and local settings. A repository's committed
.claude/settings.jsoncan only add asks and denies, so a cloned repository cannot switch Pushary off. - After three approvals of Bash in a row in one session, the phone and the Mac offer "Allow Bash for this session". Risky commands still ask, and a command the grant holds back is delivered exactly as it was before the grant.
- "Approve for this session" no longer saves a rule for a risky command, or for a wrapper such as
bash -corgit -Cthat would cover every command after it. - A preset you picked in Policies now applies on sites that have an Approvals mode set, which is nearly all of them. The Approvals mode still decides where an approval goes.
- A call your team routes to an approver keeps that approver, whatever your own Claude Code settings allow.
- An automatic update whose agent configuration refresh fails now stays pending and retries, instead of reporting success.
- A newer release now replaces a pending upgrade whose refresh keeps failing, so updates never stop.
pushary upgradeprints what failed instead of a stack trace. - Setup, upgrade and doctor no longer run a Codex binary that sits inside an app bundle when
codexon your PATH points there directly or through a symlink.
1.5.1
- Setup no longer stops at a Codex version. It writes the Pushary hooks, asks Codex to trust them, and checks Codex's answer. It used to trust the hooks itself only up to Codex 0.155.1, and on anything newer told you to open Codex and trust them by hand.
- Pushary asks Codex only when you run setup, upgrade or doctor yourself at a terminal, only through the
codexcommand on your PATH, and only if Codex has been opened on this machine before. It never starts the Codex inside the ChatGPT app, and never while a hook is running. - If Codex does not accept the trust, setup says so and tells you to open Codex, run
/hooks, and choose Trust for the Pushary hooks. When Codex is not asked, setup says why in one line and keeps the trust entries it wrote itself, as it did before. pushary upgradeasks Codex the same way after it refreshes the Codex hooks. The daily automatic update refreshes the hooks and writes the same trust entries but does not start Codex, so an install left untrusted by the old version limit is fixed on its next update either way. Machines older than 1.5.0 do not update themselves: runnpx @pushary/agent-hooks@latest upgradeonce.pushary doctorshows what Codex itself reports for each Pushary hook: trusted, untrusted, modified, not listed, or turned off. When Codex is not asked or cannot be asked, doctor says so and uses its own check instead.- Pause automatic updates on Linux to preserve phone-started sessions during daemon replacement.
- Retry incomplete configuration and daemon refreshes after an automatic installation, including when the installed version is already current.
1.5.0
- Pushary now updates itself in the background. When an agent session starts, it checks at most once a day for a newer version within the same major version, installs it into the global npm folder your hooks already run from, and refreshes each agent's config the way
pushary upgradedoes. The session never waits for it. A new major version is shown inpushary doctorand left for you to install. - Turn it off with
pushary upgrade --auto=off(and back on with--auto=on), or setPUSHARY_DISABLE_AUTOUPDATE=1in the environment your agents start from. - It does not run on Windows yet, in CI, or for a copy run through
npx, a plugin, or the Mac app. It never usessudo: if npm cannot write to the global folder, the attempt is recorded as failed and nothing changes. - This only reaches machines on 1.5.0 or later. To get there, run
npx @pushary/agent-hooks@latest upgradeonce. pushary doctorshows whether automatic updates are on and the result of the last check, andsetupanddoctortell you once when Pushary has updated itself.pushary doctorno longer calls a build newer than the npm release out of date.- The commands the CLI prints for you to copy now say
npx @pushary/agent-hooks@latest. Without@latest, npx runs whatever older version is installed globally. - A failed npm install now reports npm's reason, such as a permission error, instead of a line like
syscall mkdir.
1.4.2
- Setup trusts the Pushary hooks for you on Codex up to 0.155.1. It used to stop at 0.147.0, so on a newer Codex it wrote the hooks without trusting them, Codex never ran them, and setup still said it was done.
- On a Codex newer than the last version Pushary checked, setup says so in one line and tells you to open Codex and choose Trust for the Pushary hooks.
- The phone QR stops waiting 10 seconds before the pairing code expires, so the code is cancelled cleanly on a timeout.
- After a minute at the QR with no scan, setup offers the browser sign-in: Ctrl-C, then
npx @pushary/agent-hooks setup --connect browser. - A kill switch or standing deny on Codex now appears in your decision history, as it already did for Claude Code. If that record fails to send, it is sent again on the next matching denial.
clean --everythingalso removes the sign-in session the Mac app now keeps in the keychain.
1.4.1
cleanmasks credentials in shell lines it cannot rewrite, including output you might send to support.clean --everythingstops before changing configuration if the Mac app refuses to quit. Keychain access failures are reported as incomplete cleanup; missing items alone count as absent, and permission prompts can finish.- Cleanup passes discovered preference domains, app paths and keychain services as literal command arguments. A foreign skill named
pusharyis preserved unless the skills.sh lock identifies it as ours.
1.4.0
pushary clean --everythingremoves what the Mac app set up as well as what the CLI did. It quits the app, then removes its keychain items, preferences and caches, the editor extension it installs in Cursor and VS Code, and the skill's registration in~/.agents. A plaincleanstill leaves an installed Mac app alone, and now says so instead of printing "Clean complete." over a key it kept.cleantells you to rununset PUSHARY_API_KEYwhen your terminal still exports the key. Without it,setupin the same terminal signed you straight back in as the account you had just removed.cleanno longer leaves your key behind in its own backup of your shell profile, in earlier copies of the Cursor plugin, or in the linepushary logoutcomments out.- A line in your shell profile that still sets a real key, in a form
cleanwill not rewrite, is reported as left behind rather than skipped. clean,doctorandsetupname business@pushary.com and the Discord when they leave something broken.- A plain
cleanafter you delete the Mac app removes what the app left behind. It used to take the app's leftover locator file as proof the app was still installed, and kept every hook pointing at it. In Cursor those hooks deny whatever reaches them once there is no app to answer. cleanremoves the Pushary entry and its key from Claude Code's own backups of~/.claude.json, not only from the live file. Restoring one of those backups used to put a working key straight back. A backup from before setup keeps whatever you had there.- A file edit no rule was written for goes straight to the agent's own prompt instead of waiting for your phone first, in Claude Code's default mode and when Codex is about to ask. Over 35 days these were asked 224 times and answered from a phone twice. A rule you wrote, a preset or mode you picked, and a scope contract still reach your phone, and runs that bypass permissions are unchanged, because there is no prompt to fall back to.
- A kill switch or a standing deny enforced on this machine now appears in your decision history, as it already did through the Mac app. Only what the machine allowed was recorded before.
- Qwen Code, CodeBuddy and Qoder keep the answer you give on a permission prompt. Devin's patch and MCP calls reach the gate that was installed for them. A hook naming an agent Pushary does not recognise with
--sourcestands aside, as it already did with--agent, rather than acting as Claude Code. doctorreads hook commands written in quotes or with--source, and tells a malformed MCP registration apart from a configured server.- A hook payload that is not the shape it claims to be is refused before it can change anything.
1.3.1
- Keep human-review policies effective inside shell chains, and refuse automatic approval of quoted command substitutions.
- Check kill switches and standing denials before rejecting unreadable tool targets. Check every file in a patch; ask the agent to split a batch that crosses scope.
- Preserve symlinked agent configuration files during atomic updates.
- The installed skill recognizes file-edit capabilities across agents, including Codex patches.
pushary doctorwarns when hooks are registered for an agent this version has no profile for. Every one of them stands aside, so nothing on that agent is gated or recorded until you upgrade.- A hook that stands aside because it cannot resolve
--agentsays so in the documented[degraded]shape, so a stand-aside is countable instead of invisible.
1.2.0
Nothing to run. These are the gate's own rules, tightened where they were guessing.
- A shell command Pushary cannot read is no longer judged on the loosest rule that fits. A tool whose command arrives under a key we do not recognise used to fall through to a bare
Bashrule, which is always the permissive one; a path in the same position already refused to be judged. Both now say they have no opinion and let the agent ask you. An agent that names its command differently can declare the key instead of losing the gate. - An
--agentPushary does not recognise stops the hook rather than turning it into Claude Code. A typo in a registered command used to fetch Claude's policy and file the session under Claude's name. Every hook now stands aside and says why, because acting as the wrong agent is worse than not acting. pushary doctorcalls your hooks intact only when all of them are. It used to accept any single surviving hook as proof, so a config wiped down to one entry read as healthy.- Setup no longer counts its own folder as proof you have an agent installed. The directory it writes the config into was being read back as evidence the vendor was there, so ticking an agent once made it look installed forever. Only the agent's own binary counts now.
- The slowest call we make to Stripe carries its own deadline. Every Stripe call was bounded at five seconds, which is right for the checkout path and too tight for an invoice preview.
1.1.0
Nothing to run if you are already set up. pushary setup writes the new skill;
so does opening the Mac app, which carries its own copy.
- A scope boundary now works for a run that changes no files.
propose_scopetakespromises: boundaries that are not paths, such as who you will not email, what you will not spend, and which systems you will not open. They are shown to the approver as "Promised, not checked", recorded in the ledger, and never enforced, because the gate judges a file path and these have none. Saying so is the point: the tool used to returnratified: truefor a marketing or support run whose contract matched nothing. - The tool now reports what it will actually check.
enforceslists what the contract contains that can be checked, and an empty array means nothing in it is.hookSeen: falsemeans no hook has ever reported that session id, so the contract is stored under a key the gate will never read. Both were previously invisible: a contract that enforced nothing looked exactly like one that enforced everything. - Four glob rules that matched nothing are repaired when they are proposed.
customers,docs/,**/.env*and**/*.test.tseach validated clean and matched no file. The last two shipped as our own documented examples, so anyone who copied them to protect a root.envprotected nothing. The missing pattern is added at proposal time rather than by loosening the matcher, which would have widened every contract already live. - The approval card is budgeted from the restrictive half outwards. The allow list is the only part that grows, so cutting one joined string at the end dropped the off-limits line, the definition of done and the promises first: exactly the half a person needs in order to say no. Agent-supplied text is flattened to one line, so a promise can no longer forge a row the server wrote.
- The editor gates carry the scope path again. The Cursor and VS Code gates contained no reference to it, so approving a breach never widened the contract and every further file in the same area asked again. An over-length value is dropped rather than sent, because
ask_usercaps it and a rejected call fails closed. - A scope breach says so on the card. A breach judged by the server reached the person as a plain "Allow Edit foo.ts?", with nothing saying a boundary they personally ratified had been crossed.
pushary doctornames a tool that can take your hooks away. cc-switch replaces~/.claude/settings.jsonwholesale when you switch provider, so Pushary stops firing with no error and nothing to see. Doctor says so while the hooks are still there, and says the mechanism rather than blaming it once they are gone. It diagnoses only: it never rewrites a config because somebody else's software is installed.- The installed skill goes to 0.10.0. It breaks a task into steps before asking anything, settles every fork a file or a command can answer, and asks the rest as one numbered round. A question in the terminal is free and a push is not, so the whole round goes to the terminal when you are there and only the blocking one crosses to your phone. Setup now branches on the machine instead of assuming one path.
1.0.0
Run pushary setup and tick the agents you use. Five more are on the list: Qwen Code, CodeBuddy, Qoder, Droid and Devin CLI. Nothing changes for an agent you already had wired.
- Gate five more agents on their own hooks, not on good intentions. Qwen Code, CodeBuddy, Qoder, Droid and Devin CLI each register the same events Claude Code does, in the file and the spelling that agent uses, and each returns a verdict that agent obeys. Shell commands, writes and edits go through your policy and your phone the same way they always have. Twelve agents are now enforced rather than cooperative.
- Devin answers in its own dialect. It reads
{"decision":"approve"|"block"}where the others read Claude'spermissionDecision, so the verdict is written the way each agent reads it. A stop from your phone carries across both: it ends the turn rather than declining one tool call and letting the next one through. - Two tools are deliberately not claimed on Devin. A question answered on your phone comes back as rewritten input, and Devin's reply shape has nowhere to put it, so asking there would tell the agent "approved" and hand it the question you already answered. Pushary stands aside instead, and Devin asks you itself.
- Read the file out of a patch before judging it. Droid edits by sending a patch rather than a path, and a rule written for a file cannot match a patch body. A single-file patch is now resolved to the file it touches, so
Edit(**/.env)denies it. A patch over several files is not judged at all and goes back to the agent to ask, because one answer cannot cover several files honestly. - Never judge a call whose target cannot be read. If a tool names its file under a key Pushary does not recognise, the only rule that could match is the one written for the tool alone, and that is always the looser one. Pushary now says it has no opinion and lets the agent ask you, rather than approving on a rule that was never meant for that file.
- Leave a config alone when it is not the shape Pushary knows. These are five other vendors' files. Where a hook list is something other than a list, setup now names the key, changes nothing and reports the agent as skipped, instead of replacing what it found. It already refused to rewrite a file that would not parse; this is the same care for a file that parses and is simply shaped differently.
- Keep the directory a shell command names. An approval card that said
rm -rf *without saying where it ran was describing a different command than the one about to happen.
These five rest on each vendor's documentation and our own round trip; no vendor binary was installed to confirm them. If setup reports one as skipped, that is the check above doing its job, and the message names the key it did not recognise.
0.99.0
Nothing to run. This release is the part that runs itself: the repairs below happen on the next session start, on the machine that already has them wrong.
- Switch on a hold that an older release registered without its proof.
PermissionDeniedand Codex'sStophold only when their registration carries--hold-budget, and a registration written before 0.97.0 is present and looks correct, so nothing noticed it had stopped holding. Until now the repair waspushary upgrade, run by hand. Claude Code's registration is now checked on every session start whether or not Claude Code itself has changed, and Codex's is checked on its own session start, which had no repair of any kind before this. An auto-mode block or an auto-reviewer denial can be answered again without anyone being told to run a command. - Hooks the macOS app wrote are still left to the app, on both agents. It repairs its own, and rewriting them here would point them at a binary the app does not install.
- Read a hold budget that is not this release's as no proof, which is what the Mac app has always done.
pushary doctorsays a hook "carries no current hold budget" rather than that it predates one, because now it can mean either. - Never replace an absolute path in a hook command with a bare name. When the bin directory cannot be found, which is what a GUI-launched agent or an nvm switch does to
npm prefix -g, the rewrite is refused and the registration you already have is left alone. A hold that does not hold still works less well than it should; a hook command that no longer resolves does nothing at all, and the rewrite touches every event at once.pushary upgradeis a person at a prompt and is unchanged. - Write Codex's
config.tomlatomically, carrying its permissions over. It is now written from a session-start hook, and two sessions starting at once must not leave it half written. An atomic write replaces the file rather than its bytes, so the mode is copied across by hand: this file holds your API key, Codex creates it private to you, and it stays that way.
0.98.0
Run pushary setup so the instruction block in Codex's AGENTS.md, GEMINI.md and OpenCode's AGENTS.md carries the new line. pushary upgrade rewrites hooks and trust, not instruction files.
- Say the Codex rescue in your own voice. When you allow a command the auto-reviewer denied, the turn now resumes with "I approved this command in Pushary after the auto-reviewer denied it", because Codex hands that text to the model as your own turn. The old third-person wording read as a tool claiming someone else's permission and the model refused to run the command again.
- In the instructions Pushary writes, say that rerunning the exact command you approved after a denial is the approved path and not a bypass of an enforced gate. It sits beside the line that says never to bypass one, which is what the old wording collided with. Tied to the hook result rather than to "when Pushary reports", so a page or a diff reciting the sentence beside a command is not a rule telling the model to run it.
- Register the Cursor gate where it already sits, like every other writer, instead of taking it out and adding a fresh one on the end.
- Drop the Codex trust key of a duplicate of ours when a rewrite collapses it. Otherwise the tool that moves up into that index inherits a hash it cannot match, and Codex reads its hook as Modified and silently stops running it.
pushary doctorno longer says another tool's hook "runs before ours" or "runs after ours". No agent runs the hooks on an event in a defined order, so the line names who else holds the event and for how long, and claims nothing about which goes first.
0.97.0
Requires the Codex auto-reviewer handling in POST /api/agent/gate, Codex bodies on POST /api/agent/blocked-action/claim, the blocked-action/v2 Claude identity and sessionCwd on the gate request before rollout. Run pushary upgrade so Claude Code's PermissionDenied and Codex's Stop are registered with the hold budget, Codex gives Stop the full hook budget, and Codex trusts it again.
- When Codex runs with
--approve-for-me, stop asking you before its auto-reviewer.PreToolUseandPermissionRequesthave no opinion under the reviewer, after the kill switch and a standing deny, so nothing the reviewer allows reaches your phone. - When the auto-reviewer denies a shell command, hold the turn end and ask once whether to allow that exact command. A yes continues the turn, Codex runs the same command again, and the retry skips the reviewer once. A no, or no answer, lets the denial stand. Denials of
apply_patchand MCP calls are not asked about yet. The request names the session's directory as well as the command's, so the card can say where the command runs. - Hold a blocked call only when the hook can prove its host waits that long: the
--hold-budget=<seconds>argument its registration now carries, or the budget the Mac app hands down inPUSHARY_HOLD_BUDGET_SECONDS. The hold never waits past that budget less its reserves. Without proof, which is an install registered before this release, the Claude plugin's older entry, an update that skipped--finish-upgradeor a Mac app built before it, a Claude block sends theAuto mode blocked <tool>notification instead, and a Codex turn end reads no denial, so it is still asked about once the hook is upgraded. - Build the
blocked-action/v2identity for a Claude call, which includes its working directory, so the retry's hint and claim match the server's grant. A Claude call with no working directory is not asked about. Keep the retry hint for the grant's two minutes rather than ten. pushary doctorwarns when aPermissionDeniedor CodexStopregistration carries no hold budget.
0.96.0
Requires the auto-mode block handling in POST /api/agent/gate and the POST /api/agent/blocked-action/claim route before rollout. Run pushary upgrade so Claude Code gives the PermissionDenied hook the full approval window.
- When Claude Code's auto mode blocks a call, hold the agent and ask once whether to allow that exact call. A yes retries it and the retry runs. A no, or no answer, lets the block stand.
- Stop sending a separate "Auto-mode blocked" notification and remove
PUSHARY_AUTOMODE_RETRY. The server now decides whether a block is asked about, so the kill switch, a standing deny and your approval mode all apply.
0.95.8
- Stop
cleanleaving a key it installed in Claude Code, Codex and Gemini when the Mac app is installed. An entry is kept only when its key is the one the Mac app holds; when the app has never signed in, the key setup recorded is removed. - Keep
~/.pushary/bridge-authwhen the Mac app is installed.cleanused to delete the app's bridge key.
0.95.7
- Let Gemini's own prompt decide in When I'm out when Pushary cannot create the approval question, instead of refusing the call. Every time still refuses, and says Pushary could not create a verifiable approval.
- Refuse approvals an unattended Gemini bridge cannot show locally with a specific reason (no local input, no phone answer in time, handled on the machine, no device connected), instead of passing them to Gemini's unexplained headless denial.
- Withdraw the phone question before a Codex bridge gives up on a Terminal or Updates question, so an answer that arrives during the withdrawal still counts.
- Describe what happens when you don't answer in
pushary doctorandpushary waitin the current mode words: When I'm out asks your phone when you are away or Pushary cannot tell, then hands back to the agent's own prompt.
0.95.6
- Separate synthetic answer tests from real hook activation and report the actual answering surface.
- Cancel known pending diagnostic questions on interrupted or unanswered exits without fabricating approval.
- Add Claude Code activation evidence for a unique scratch-file approval, correlated with the observed tool result.
- Report managed-control readiness separately from hook configuration and phone delivery, refreshing credentials, service state and remote proof after interactive waits.
0.95.5
- Preserve the full approval window while allowing the final poll time to return; keep late denials and cancellation authoritative.
- Bundle corrected VS Code read classification and Cursor file-gate fallback, including native shell/MCP prompts when a gate script is missing.
- Share Claude hook ownership with the server to avoid duplicate decisions while retaining native-mode handoff.
- Keep supported-agent setup guidance and policy scope aligned with the shared agent manifest.
0.95.4
- Keep inactive/offline login results truthful, with one JSON result and no duplicate key minting to recheck readiness.
- Preserve authentication blocks until readiness is verified; recover the verified saved account while respecting explicit control off.
- Keep setup and CLI help accurate about approval delivery modes.
0.95.3
- Verify account readiness after phone pairing before activating approvals or starting managed services.
- Preserve useful configuration while offline, with consistent unverified output and exit codes instead of a successful activation claim.
- Reuse the saved key when setup is rerun after payment or connectivity recovers; preserve explicit phone-start opt-outs.
0.95.2
- Add
pushary coworkand connector-only setup aliases for Claude Desktop, Chat and Cowork, without acquiring a CLI key or installing local hooks. - Bundle the canonical Cowork skill and expose setup steps, standing instructions and a real user-answer test prompt.
- Explain cooperative connector limits and keep native permission hooks and managed session control separate.
0.95.0
Requires the additive session-control and encrypted transcript-preview server routes before rollout.
- Capture opted-in direct Claude terminal transcripts through the daemon, including Mac-owned hooks when this CLI is installed. Normalize explicit Codex rollout paths.
- Keep native terminals open for ordinary phone messages; require explicit phone takeover and resume the verified native session identity.
- Wait for native process exit before SDK control, preserve queued instructions across mode changes, and acknowledge only accepted commands.
- Stream redacted, encrypted SDK text previews using the existing acknowledged session key; completed transcript records remain authoritative.
0.94.0
- Align Node compatibility with setup dependencies; unsupported runtimes receive an upgrade message before a command loads.
0.93.1
- Attach stable usage report IDs and preserve the report timestamp across retries so the server counts a replay once.
- Report restricted agent-key scope accurately and fail closed on unknown identity scopes.
- Requires the additive server accounting and key-capability changes from #1303 before rollout.
0.93.0
Requires Node.js 20.3.0 or newer for shared cancellation signals.
- Register the expanded Claude and Codex hook coverage from the September 4 changes; preserve child-agent identity during compaction.
- Share one deadline across mode lookup, question reconciliation, event reporting, and command acknowledgement. Retain unresolved question markers for retry.
- Deliver queued phone instructions on UserPromptSubmit and keep observational hooks from consuming them.
- Redact credentials in notification activity, phone pushes, and unrecognized failure messages.
- Diagnose modified Codex hook definitions against their installed trust hashes.
0.92.1
A rejected key is an answer, not an empty poll
pollAgent returned { command: null, mode: null } for every non-OK response,
so a 401 was indistinguishable from a healthy poll with nothing queued. That
landed on the success branch of startCommandPoller, which resets errorStreak
to zero, so the backoff never engaged and the wrapper polled a dead key at the
idle cadence forever. Because mode was null, onModeState never fired, and
onModeState is the only place dualMode sets killed and stops the active
leg. A wrapper whose key the server rejects therefore reported kill=false to
itself indefinitely: remote stop could not reach it, phone messages never
landed, and nothing was printed.
PollResult now carries rejected, set only for 401 and 403, which is the same
line fetchModeState already drew between an answer and a blip. A rejected poll
takes the error path, so the existing exponential backoff applies, and the
wrapper prints once that remote stop and phone messages are not active. A 500 or
a dropped connection is still a blip and still retried at the normal cadence.
A rejected response is also no longer trusted for its contents: neither the command nor the mode state it carries is applied.
0.92.0
A Claude Code upgrade registers the events it just gained
CLAUDE_HOOK_EVENTS gates each hook on the oldest Claude Code that understands
it, and the supported set was filtered by the version detected when the hooks
were written. Nothing re-ran that filter except pushary upgrade, so an event
your Claude Code gained reached you when Pushary next shipped rather than when
Claude did: the hook sat in the table, supported by your CLI, and unregistered.
SessionStart now asks the question, because that is the moment an upgrade becomes observable. The answer costs about 5 ms of local reads and writes nothing unless the event set actually moved. A machine wired against an older Claude picks up the newer events on its next session.
It leaves three things alone. Hooks the macOS app wrote are never rewritten: the
app owns that wiring and the CLI's writer would replace its locator with the npm
bin. A patch bump that adds no events refreshes only the receipt, so settings.json
is not rewritten every time Claude Code ships. A machine with nothing wired is
left to setup.
The settings write is now atomic — temp file, fsync, rename — because several sessions can start in the same moment and two plain writes interleaving is a torn settings.json.
0.91.0
Setup adopts the key the Mac app is already signed in with
The Mac app authenticates through Clerk and writes its API key into every agent
config it wires, but never into ~/.pushary/config.json. Setup's key ladder did
not look there, so a Mac the app had already connected still fell through to the
pairing QR and minted a second key for an account that was already live.
The ladder gains one rung between the environment and pairing: a key the Mac app
wrote, probed against the server exactly like the stored and environment rungs
are, and skipped whenever --key was passed or --connect app was typed. Both
of those still mean what they meant.
0.90.2
The Mac app and CLI coexist without a hook takeover
Setup keeps live Mac app hooks in place without prompting, while verifying that the app and mobile setup belong to the same Pushary tenant. Distinct keys for the same tenant coexist; a different or unverifiable account is reported without silently replacing the app's wiring. Stale app hooks are repaired by the CLI.
An explicit --take-over-hooks remains reversible. Clean restores the app's
hooks, MCP credentials, and Codex trust from a retry-safe receipt before removing
the CLI, and dry runs no longer claim that restoration already happened.
0.90.1
Daemon logs distinguish health from failure
Successful daemon startup and phone-requested launches now go to the normal log, leaving the error log for failures that need attention.
Command-store tests never touch live agent state
The pending command store accepts an explicit directory override. Tests use a fresh temporary store, so parallel and sandboxed runs cannot read, overwrite, or prune commands belonging to a real agent session.
0.90.0
A hook that needs an answer is allowed to wait for one
Claude Code hooks are now registered at the 600 second budget Claude itself allows, so a policy that waits two minutes for a phone approval is no longer cut off at 120 seconds and turned into a denial the user never saw. Anything a hook does that is not a decision keeps its short timeout.
A phone card is now withdrawn when the tool it guarded completes or the agent's own client denies it, so an approval cannot arrive for something that already finished.
The prompts that never reached the phone
Codex permission requests are no longer filtered by a shell matcher, so a browser use request, a managed network request and an MCP tool request all reach the phone the same way a shell escalation does. Codex in bypass mode now decides before the tool runs rather than after it, which is what push-first has always meant everywhere else.
Gemini reports its turn boundary through AfterAgent and its teardown through SessionEnd, so a finished Gemini turn is a finished turn rather than a session that stayed open.
A finished subagent now ends its own session instead of ending its parent's, and appears under the session that started it. Context compaction is reported as itself rather than as a tool that ran.
Pushary's own tools are never gated by Pushary, on the server exactly as in the hook, so asking the user a question can no longer wait on approval to ask it.
Sessions end, and a daemon that cannot log in says so
A session that goes quiet is closed rather than left standing forever, and the
daemon reaps the sessions it started. pushary daemon status lists what it is
holding, how old each one is, and how much memory it is using.
A daemon whose key is rejected now records that and stops, instead of restarting
into the same rejection until something notices. pushary doctor says the daemon
needs a login, and logging in clears it.
Setup and doctor tell the truth about this machine
pushary doctor names the other tools holding the same agent hooks and how long
each of them waits, checks that the bridge the Mac app installed can be found,
and reports which Node the hooks will actually run under.
pushary setup gained --verify none for an unattended install, prints what it
would also do during a dry run, offers a way out of pairing, and can take over
hooks another tool is holding with --take-over-hooks. pushary mode now sets a
site's mode permanently unless told how long to keep it.
Machine identity and clean
pushary clean leaves the machine identity shared with the Mac app intact, keeps
one entry rather than two, and treats the identity file as the CLI's to own and
the app's to read. Transcript sync seals its recovery ledger and admits a gap
rather than syncing bookkeeping around it.
0.89.13
Synced transcripts survive large turns and restarts safely
Oversized file reads and diffs are shortened before encryption instead of being rejected by the server, with the removed UTF-8 byte count recorded in the turn. Stable HMAC identifiers prevent transcript contents leaking through record ids, and a resumed session reuses its persisted encryption key rather than conflicting with the key already registered for that session.
Transcript recipients are now pinned on first use and later key changes stop sync
until the user explicitly runs pushary transcripts trust --rotate. First use
also fails closed when that pin cannot be stored, so no session key is wrapped to
an unrecorded recipient. New sessions can independently wrap their key for the
owner's phone and an optional audited compliance-recovery recipient; installations
without one remain compatible.
0.89.12
Phone control installs as a native service, without a VM
Fresh setup enables phone-start when a supported provider and the operating system's own per-user service manager are available: launchd on macOS, systemd on Linux, and Task Scheduler on Windows. Existing installs keep their stored choice. Service replacement is transactional, verifies the new process before committing, and restores the previous running or stopped state on failure.
Claude Code, Codex and Gemini sessions started from the phone now use the same supervised control path. Setup checks both the local heartbeat and the server's view of the machine before calling control ready.
Fresh and partial installs repair themselves
Setup now verifies every global command, its local JavaScript imports, and the Cursor and VS Code runtime assets before reusing an installed version. A same-version package missing any of them is reinstalled instead of failing the same setup forever. Fresh installs also cover Codex on Windows and recover from partially configured providers without disturbing unrelated settings.
The CLI and macOS app keep their own wiring
Cursor setup writes one gate per event even when the other installer ran first.
pushary clean removes the CLI gate while preserving the app's bridge, and
pushary doctor validates the MCP entry from the installer that actually wired
Cursor. This keeps mixed CLI/app machines working and avoids false repair
failures on app-only installs.
Transcript control works in every shell
Setup discloses encrypted transcript sync and its audited compliance-recovery
path before enabling it for a new install; older installs remain off until setup
runs again. pushary setup --transcripts on|off now provides the same persistent
choice in POSIX shells, PowerShell and Command Prompt. The existing
PUSHARY_TRANSCRIPTS environment override remains compatible.
0.89.11
Phone-spawned Codex and Gemini sessions record their conversation again
Both bridges relay their turns by default and stop only when told to. The launchers told them to stop on every run: the value they passed was an equality test against an environment variable nobody sets, which is false whenever it is absent and never the "unset" the bridge was looking for. The relay was therefore never built, and a session started from the phone had nothing to show but its task title. The opt-out is now passed only when it is asked for.
An agent standing by stops reading as working
pushary claude and the Gemini bridge reported that a session had started, that
it was still alive, and that it had ended, with nothing in between. Their
heartbeat kept resetting the clock that would otherwise have aged the session out
of "active", so the phone showed a working agent for one that had been waiting at
its prompt since its last reply. That is exactly when someone is looking at the
screen deciding whether to send it something. Both now report the turn boundary
they already detect, as the Codex bridge always has.
Taking a session over from your phone says so, and is ready when you ask
An instruction from the phone hands the terminal to Pushary so it can drive an agent that would otherwise sit idle. That has not changed. The handover is now announced before the session is signalled, instead of the terminal appearing to lose its agent for no visible reason. The Agent SDK is resolved when the session starts rather than during the handover, so the wait is gone, and a machine that cannot reach npm reports the instruction as undelivered instead of accepting it and then dropping it. An instruction that is dropped now says it was.
0.89.10
Phone control delivery is crash-safe
Phone instructions now use per-command receipts across polling and relay delivery. A process restart acknowledges the recorded outcome instead of silently losing or repeating an instruction, multiple queued commands no longer overwrite each other, and expired receipts are pruned when new commands arrive.
Setup and clean reverse only Pushary-owned changes
Setup records the Claude and Hermes configuration it replaces. Clean restores that prior shape without deleting later user edits, and retains the receipt when service removal, Hermes rollback, or plugin uninstall is incomplete so a retry can finish safely. Installations created by older releases keep their compatible best-effort cleanup path.
Native permission modes keep their intended owner
Codex now reads the permission mode it already reports, and Claude
PermissionRequest remains answerable from Pushary after the agent itself asks
for a human. Kill switches and standing denies still take precedence.
0.89.9
Setup proves managed control reaches the server
The background daemon now records successful heartbeats separately from successful control-queue responses. Setup and upgrade wait for both before reporting managed control ready, and doctor reports either missing proof.
An authenticated, once-per-minute challenge round trip gives the server the same readiness evidence without launching an agent or changing queued work. Machine status exposes that observation additively so current clients keep their existing fields while newer CLIs can verify capabilities end to end.
0.89.8
Daemon upgrades keep a recoverable service
Background-service replacement now records the previous definition and native running state in an owner-only transaction receipt before changing launchd, systemd or Task Scheduler. A failed or interrupted replacement restores that service before another upgrade is attempted, and verifies whether it should be running or stopped.
Native probe errors are not treated as proof that a service disappeared, and rollback preserves an administratively disabled service instead of enabling it.
pushary clean now proves the background service was removed before changing
agent configuration or local state. If removal cannot be proven, clean stops
with the existing installation intact so it can be repaired and retried.
0.89.7
clean no longer uninstalls the macOS app along with the CLI
clean removed ~/.pushary whole. That directory is not only the CLI's: the Mac
app roots its entire runtime there, so the locator every hook execs, the socket
the app listens on, its gate state and its spool all went with it. It also
stripped the app's hook entries out of the agent configs. The app went on
running and went on reporting itself connected, while nothing reached it.
It now removes what the CLI owns and leaves bin, run, spool and plugins.
Hook entries are attributed by the command they name, so the app keeps its own.
disconnect is unchanged and still disconnects an agent whichever product
connected it.
The mcpServers.pushary entry and the VS Code plugin directory cannot be
attributed to either product, so they are kept when the app is installed and
removed when it is not. A CLI-only clean still leaves no API key on disk.
clean handles OpenCode
setup installs an OpenCode plugin and an MCP entry and clean walked past both,
so a command that says it removes all Pushary configuration left the sixth agent
wired. A pushary.js somebody else wrote, or one the Mac app wrote, is left
alone.
0.89.3
Doctor recognizes hooks installed by the macOS app
Claude Code, Codex and Gemini hooks that point at the shared native
pushary-bridge now pass the same presence, executable and timeout checks as
their npm hook binaries. Doctor no longer tells a Mac-managed install to repair
hooks that are already installed and running.
0.89.2
Multi-question prompts keep their real answer shape
Claude still sends each question separately so older clients can answer it, but each card now carries its canonical metadata too. Current notch, phone, web and Slack surfaces keep descriptions, write-in answers and every multi-select value instead of collapsing a multi-select to one top-level option.
0.89.0
Hermes approvals go to the phone, and setup wires it
setup --agents hermes now selects Pushary as the Hermes approval transport,
with transport_fallback: builtin. Hermes owns the timeout (300s) and the
once/session/always/deny choices, and persists the last two, so a repeated
approval can become a standing rule instead of being asked forever. The
fallback is what makes selecting it safe: a missing key, an unreachable
Pushary, or no connected device falls back to the terminal prompt rather than
blocking work.
The note explaining why setup writes ~/.hermes/config.yaml directly rather
than running hermes plugins enable was half wrong. Current Hermes does
resolve pip-installed plugins in both list and enable; the reason to write
the file is the rule against executing an agent's binary, and nothing else.
pushary upgrade named a package that does not exist
It printed pip install --upgrade pushary-hermes. That package is not on PyPI.
The real one is hermes-plugin-pushary, and the upgrade path for it is setup,
which installs into the interpreter Hermes actually runs in. Now points there,
like every other agent in that list.
0.88.1
One install, whichever surface writes it
The generated hook spec now describes the whole install, not only the hooks: the
MCP entry for each agent, the managed block in AGENTS.md or GEMINI.md, and the
skill. The CLI writes exactly what it wrote before. The Mac app reads the same
spec, so an install from the app and an install from pushary setup leave the
same files behind, and Repair can put back a tool entry as well as a hook.
Elicitation is no longer CLI-only in the spec: a typed answer can now come back
from a settlement, so the Mac app writes that hook too.
One predicate for Ctrl-C at a prompt
Setup carried its own test for an inquirer cancellation and matched a different
string from the one in cli/exit.ts. Both agreed on inquirer's current wording
and would have disagreed the day it changed, printing "Setup failed" over a
deliberate Ctrl-C. One predicate now, matching the union of what both matched.
0.87.1
Unanswered approvals hand off once and fail closed
The installed agent instructions now distinguish a live timeout from cancelled, missing and unavailable questions. They poll once, cancel before moving the decision into the current client, reconcile the cancellation race once, and stop when Pushary cannot safely fence the question state.
0.86.0
Approving a plan from your phone
Exiting plan mode blocked the terminal and pushed nothing, so an away user's agent
sat waiting on the one approval that decides everything after it. ExitPlanMode was
never in the hook's tool matcher, and plan is a mode Pushary steps aside for
entirely, so matching it alone would not have helped either.
The plan approval now reaches your phone before Pushary steps aside, and it carries the plan itself, redacted and capped like any diff, so you are approving something you can read rather than a bare tool name. Two options rather than the terminal's three: a hook cannot set the permission mode the session lands in, so offering that choice would have quietly dropped half of what you picked.
Falls back to the terminal exactly as before whenever the phone cannot answer.
0.85.2
One agent-protocol renderer for CLI and native approvals
Claude Code, Codex and Gemini hook output now comes from the shared contracts renderer also used by the native Mac gate. This keeps the CLI wire bytes unchanged while preventing the native bridge and published hooks from drifting into different allow or deny shapes.
0.85.1
A rule you wrote is now honoured in auto mode
A policy set to deny a tool outright was computed and then discarded in the three
Claude Code permission modes Pushary steps aside for: auto, plan and dontAsk.
auto is the default on Pro, Max and Team, so this was most sessions. The native
classifier has never heard of your rule, so it would approve what you had forbidden.
The defer exists to stop Pushary double-prompting on top of Claude Code's own prompt. A deny prompts nobody, so that reasoning never covered it. A standing deny now sits with the kill switch, ahead of the defer, and applies in every mode.
Approvals are unchanged: auto still defers them, and a tool the classifier
decides to ask about is still reclaimed through PermissionRequest and pushed to
your phone. This only closes the case where a rule of yours was silently dropped.
The Codex, Gemini and SDK wrapper paths already behaved this way. This brings the Claude Code path in line with them.
0.85.0
A hook command that cannot outlive what it points at
Every hook command written into ~/.claude/settings.json, ~/.codex/hooks.json
and ~/.gemini/settings.json, the free bell included, is now wrapped so that a missing binary, a removed
node, a changed npm prefix or a moved home directory exits 0 and the agent reads
"no opinion". Before, any of those turned every tool call into a failing hook.
The guard is tested on every shell that can be /bin/sh, including dash, and with
node off PATH, which used to exit 127 and print on every tool call.
Codex hashes the exact command string to trust a hook, so pushary upgrade rewrites
the trust entry when it re-applies the hooks.
Hooks registered for the CLI you have
Claude Code events are registered from a table with a version floor per event.
SubagentStart, SubagentStop, PreCompact, PostCompact and TeammateIdle
are added on a CLI that understands them, and pushary upgrade re-registers
against the CLI you have now. Disconnect removes every event in the table; a
hand-typed list used to leave the five observability events behind.
pushary disconnect <agent>
Removes the MCP server, the hooks, the skill and, for Codex, the trust entries and
the API key from config.toml, for one agent, leaving the others alone. The bin
map entry was written in a shape the build silently dropped, and the command read
the dispatcher's own token as the agent name; both are fixed before this, its
first release, and a test now holds every bin entry to the one shape tsup builds.
A kill switch per hook path
~/.pushary/admission-rules.json ({"version":1,"disabled":["claude:PostToolUse"]})
silences one event on one agent, or one agent entirely, without a release. Every
hook binary reads its input through this check, from the same file the macOS
helper reads, so one file governs both transports. Deny-only: nothing in the file
can enable a hook.
PostToolUse no longer pays two round trips per tool call
The telemetry hooks reuse the mode state the gate fetched for the same session within the last 60 seconds. The gate itself still reads it live, so a remote stop and a scope contract are never served stale where they decide anything.
0.84.1
Remote starts survive lost acknowledgements without double-launching
A phone-started agent now claims exclusive process ownership before the provider starts. If the daemon restarts or the launch acknowledgement is lost, retrying the same request reconnects to the existing process instead of starting a second one.
Repository approval scopes reach Codex and Gemini
Codex and Gemini sessions now carry their repository identity through approval requests, so repository-scoped rules stay inside the repository that created them.
Encrypted transcripts retry safely
Transcript writers now retry while a session key is temporarily unavailable and stop on a real key conflict, preventing undecryptable transcript entries from being accepted or silently dropped.
0.84.0
The phone can start an agent on a machine that is not running one
The phone could reach a session that already existed. Starting one meant being at
the keyboard. pushary setup and pushary claude now bring up a small background
process, pushary-daemon, which watches for a launch requested from the phone and
starts a real session with the prompt you sent.
It is a poller over a durable queue, not an open socket. Idle, it asks the server for work every 30 seconds and sends one heartbeat a minute, and it carries a prompt, never any code. One request is in flight at a time, each bounded by its own timeout, with a spawn rate cap so a queue backlog cannot fill a machine with agents. Exactly one daemon runs per machine: a second one asks the first to hand over rather than racing it, and it declines to take over from a daemon that is still healthy.
This is a background process that was not running before. pushary doctor reports
whether it is up, and pushary daemon in a terminal shows you what it is doing.
A launch is now acknowledged rather than assumed. The daemon claims a request, confirms the session it started, and the phone shows what actually happened, including the failure when a launch does not come up.
Always allowing a tool no longer has to mean everywhere
"Approve and always allow" wrote one kind of rule: this tool, this whole
workspace, until you delete it. That is the right scope for Read and far too
wide for a push.
The approval now offers the scope alongside it. Workspace is the previous behaviour. Repository binds the rule to the repository the ask came from, so a rule written in one checkout stops governing another. Session binds it to the run you are watching and goes away with it.
An older Pushary never sees the narrower rules. A CLI that does not ask for them is sent workspace-wide rules only, so a rule you scoped to one session cannot be quietly applied everywhere by something released before the idea existed.
AskUserQuestion reaches the phone with its descriptions
A Claude Code question carries a header, a description per option, and sometimes permission to pick more than one. The phone received the bare labels. It now receives the question the way the agent wrote it.
One question goes out as one card carrying the whole shape. Every surface that does not know that shape, the web decision page and Slack among them, still sees an ordinary choice list and answers with a label, which the server reads back onto the question. Two or more questions go out one at a time, which is what every surface has always been able to answer: a bare label cannot say which of several questions it belongs to, and a half-filled answer is worse for the agent than being asked again in the terminal.
Codex and Gemini sessions started from the phone stay live
A phone-started Codex or Gemini run now goes through a bridge that survives the connection dropping and resumes the same session instead of opening a new one. The machine advertises only what it can actually do, so a Gemini CLI too old for the bridge is not offered as one, and the phone stops showing controls that would fail.
Stopping is acknowledged the same way. A stop request names the session and the process it belongs to, and the machine proves it owns that process before acting on it, so a recycled pid cannot be mistaken for the agent you asked to stop.
0.83.0 to 0.83.2
Three releases went out without an entry here. In order: ask_user became
idempotent, so a create that gets retried after a timeout resolves to the question
already asked instead of buzzing a phone twice. The pairing QR stopped encoding a
code only a browser could resolve. And the gate's two shared pieces, the verdict a
policy produces and the "most specific rule wins" precedence, moved into the
package the CLI and the server both read, with no change to what either decides.
0.82.0
Setup reads properly when an agent is the one running it
An agent that runs setup relays its output to you, and two things in
that output were written for a terminal that was not there.
Colour was unconditional. Piped into an agent's transcript, ten raw escape sequences arrived as literal text around the parts you needed to read. Colour now follows the same rule the rest of the CLI already used, so it is on at a terminal and off everywhere else.
The app download was unreachable until you gave up. "No app?" was answered with the command for approving in a browser, which sends you away from the phone at the moment you were being asked to install it, and the download link appeared only after the fifteen minute wait expired. The header now names https://pushary.com/download first and says the wait continues while you install.
The pairing QR is smaller
The QR encodes a link, and the link's length decides the QR's size. It carried the pairing id and the public key, 114 characters, which drew 22 rows of blocks. Against a server that offers one, the CLI now uses a short link instead: 33 characters, 16 rows, and small enough to survive being relayed through something that truncates.
Nothing depends on it. Against an older server, or if the short link
cannot be issued, setup uses the long one exactly as before, and the
pushary:// link is never shortened at all.
0.81.0
The idle ping now honours a Terminal mode set in the dashboard
"Your agent is waiting" pushes checked your delivery mode by reading the temporary override only. Choosing Terminal in the dashboard or the app does not write an override, it writes a standing rule, so the check never fired and the ping buzzed a phone whose owner had asked not to be reached.
The ping now declares itself a task update, which puts it behind the same server-side gate as everything else the agent sends unprompted. That gate sees the standing rule, the kill switch and a mute. It also means your updates dial governs idle pings: set updates to Off and they stop.
0.80.1
The bell's heartbeat directory is private to you now
pushary bell counts how many agents are live by keeping one empty file
per session in the system temp directory. On macOS that is already a
per-user directory, so this was fine. On Linux it is the shared /tmp,
where it was not: the first user to create pushary-bell owned it, and
every other user on the box got a permission error and silently counted
zero agents. Anyone who could write to it could also inflate the count
and trigger the upgrade line.
The directory is now per uid and 0700, the heartbeats inside it 0600,
and the sweep judges an entry with lstat so a planted symlink is dropped
rather than followed. Nothing about the bell itself changes, and it still
makes no network calls.
0.80.0
A bell, free, for when you only need to know it finished
npx @pushary/agent-hooks@latest bell makes a noise in your terminal and raises
a desktop notification when Claude Code says an agent finished or is waiting on
you. No account, no API key, no network call. Nothing leaves your machine, and
pushary bell --off removes it without touching anything else.
If you run one agent, that is genuinely all you need and we would rather you did not pay for more. Above three agents at once the bell says so, once a day, and then stops talking, because a bell cannot tell you which of them is asking and cannot reach you once you have walked away.
If you got here from claude config set --global preferrednotifchannel terminal_bell and the unknown option '--global' error, the README now answers
that directly.
Setup waits for you to tap, instead of trusting that a push went out
Setup used to finish on a delivered notification. A notification that arrives and cannot be answered looks exactly the same at that point, and that is the most common way this quietly breaks: approvals keep arriving and keep going unanswered.
So setup now sends a real question and waits for you to answer it, and only says
Setup complete, and proven. once you have. If nothing comes back it tells you
rather than congratulating you. It only does this when someone is actually
there, which includes a pairing you just scanned, and never in CI. Pass
--verify push for the old behaviour, or --verify none to skip the check.
pushary doctor --roundtrip runs the same check and now says the same things
about it.
Your agent works out when to reach you, without being told
The bundled skill used to describe itself only in terms of what you might say to it: ping me on my phone, run this overnight. That only fires when you are there to say something. It now also describes the moments that are true of the work itself: about to do something irreversible, about to spend money or deploy, blocked on a decision that is not the agent's to make, or a long task finishing with nobody watching.
Everything it matched before, it still matches. Re-run
npx @pushary/agent-hooks setup to update the copy your agents already have.
0.79.0
Your agent now says what kind of update it is sending, so you can route it
Pushary can now send task updates somewhere different from questions: approvals can wait at your keyboard while completions still buzz your phone, or the other way round. That only works if the agent says which one it is sending.
The instructions setup writes now tell your agent to pass a context type when a task finishes. Without it a completion looks like any other notification, and the setting you chose for task updates has nothing to act on.
Nothing changes for questions and approvals, and nothing you have configured
needs revisiting. Re-run npx @pushary/agent-hooks setup to update the
instructions your agents already have.
0.78.0
Your agent can ask you several things at once, and reach your phone for all of them
An agent that stops to ask you something often asks more than one thing in the same breath. Until this release the hook only recognised a single question, so two or more meant nothing arrived on your phone at all and the whole exchange waited at the terminal. That is exactly the moment this product exists for.
Up to four questions now come through in turn. The notification says which one you are on, "(2 of 3)", so the first does not look like the last.
If any question goes unanswered, whether you deferred it, it timed out, or no device was reachable, the entire exchange goes back to the terminal, including anything you already answered. A partly filled answer sheet would leave your agent guessing at questions it never put to you, and being asked twice is better than being answered wrongly.
The whole exchange shares one wait budget. Four questions do not cost four times the wait you agreed to.
Questions that let you tick several options at once still go to the terminal. Those answers travel as a single piece of text and the packing is not something this release can verify, so it is left alone rather than guessed at.
0.77.0
Stop ends the process
Until this release Stop was a gate verdict. Every pending and new tool call was denied, which is the guarantee and has not changed, but the agent kept running, kept thinking and kept spending tokens. For someone who hits Stop because something is going wrong, "it can no longer act" and "it stopped" are different promises.
- every session event reports its own pid, so the server can name the process
- the daemon advertises a
stopcapability, which is how the server knows a machine can act on one at all - the daemon applies stops from the drain with SIGTERM, and refuses to signal its own pid, since killing itself would take every other session's stop too
Two additive wire changes, both safe in either direction: pid on the event
payload, which an older server ignores, and stops on the drain response, which
an older daemon ignores.
0.76.1
Actually closing the spawn argv injection
0.76.0 shipped under the message "closing the spawn argv injection" and did not.
It sanitised model and cwd but not prompt, and prompt is the field the
daemon passes as the token immediately after -p, so a flag-shaped prompt was
read back as a flag:
['claude','--remote','-p','--permission-mode=dontAsk'] -> {permissionMode:'dontAsk'}dontAsk is a full-defer mode, so the gate stopped asking about every tool for
that session.
The server-side rejection landed in production first and is what actually
protects users, 0.76.0 included, with no upgrade required. This release carries
the defence in depth: buildSpawnLaunch returns null for a flag-shaped prompt,
so a daemon never builds that argv even if the server is bypassed.
0.76.0
A phone-supplied field could switch your approval gate off
pushary claude --remote sessions started from the phone build their command
line from fields the request supplies. The wrapper then read that command line
back to learn what it had been asked to do, and it scanned every position without
knowing which ones were a flag's value rather than a flag.
So a prompt or a model of --permission-mode=dontAsk was read as a real
permission mode. dontAsk, plan and auto all defer to the agent's own prompt
unconditionally, which means the session stopped asking you for approvals
entirely, and the decision log recorded a session that was never gated. A clean
approval history that is not true is worse than a missing one.
Flag values are now consumed with the flag that owns them, so nothing a request
supplies can be mistaken for an instruction. A --permission-mode you passed
yourself still works, in both the --permission-mode plan and
--permission-mode=plan forms.
The server that queues these sessions now also refuses flag-shaped model and working-directory values, so an out-of-date CLI is protected against those two. It cannot do the same for the prompt, which is free text you are entitled to start with a dash, so updating is what closes this one properly.
Reaching it needed an authenticated session on your own account, spawning to your own machine, with the daemon running. It is not cross-tenant and not remote code execution. It matters most where the person spawning is not the person governed: Team approval routing, Partner, and any case where a phone session or API key has been compromised.
0.75.0
An agent can now run the install
Pasting "install pushary for me" into Claude Code, Codex or Cursor exited 3 before doing anything:
! No API key, and no terminal to sign in from.The agent runs setup with stdin and stdout piped, so isTTY is false, and app
pairing sat behind the same check as browser login. That check was answering the
wrong question. Pairing needs a person, not a terminal, and the agent shows its
output to one, so a QR written to stdout is read just as well as a QR drawn on a
terminal.
Setup now pairs whenever it is not running in CI. It prints the QR, the tappable link and the fingerprint, and waits for the scan, so the whole install is one command with nothing to paste:
npx @pushary/agent-hooks@latest setupBrowser login still requires a real terminal, because it opens a browser on the machine and waits for a redirect. CI still declines to pair, so no build hangs drawing a QR into a log nobody is watching.
The bundled skill told agents to do the opposite: hand the user a signup link and wait for them to paste a key back. It now says to run setup and show the user the QR.
The failure message above also had to change, because after this it was naming a
cause that no longer applies. Reaching that branch now means CI, --skip-phone,
or a connect mode other than app, so setup says which of those it hit instead
of blaming a missing terminal. A headless run with no flags is the one worth
calling out: it used to be told to drop into a browser login it cannot open.
0.74.0
The terminal went quiet while your phone was deciding
A gated tool call sent the push and then blocked, writing nothing. Claude Code showed a bare spinner. If you missed the notification there was no way to tell that a question existed, let alone where to answer it, and the terminal prompt only appeared once the wait expired.
The hook now says so before it blocks:
[pushary] Sent to your phone. Waiting 10s, then this terminal takes over.
Answer here instead: https://pushary.com/dashboard/agentThe wait line prints per question, because each question is its own blocking moment with its own countdown. The URL prints once per session, because it is the same page every time and repeating it on every gated call is noise.
That URL is the signed-in dashboard, not the /decide link the push carries.
The decide link is a bearer credential: anyone holding it can answer. Handing it
to the agent that is being gated would let it approve its own action, so the
hook is only ever told about the page that requires your login.
Servers that predate this send no URL, and the hook just prints the wait line.
0.73.0
Codex could finish setup with no skill installed
If your machine already uses skills.sh, setup installs the Pushary skill
through that CLI so the install registers rather than being invisible. For
Codex, skills add --agent codex prints "copy to Codex", says "Installation
complete" and exits 0, then writes only ~/.agents/skills/pushary. Nothing
lands in ~/.codex/skills, which is where Codex reads. Setup took the exit
code as proof, skipped the copy it ships, and Codex ended up with no skill.
Doctor caught it, and then gave advice that could not work: clean and set up again reinstalled exactly the same way and failed the same check.
The exit code no longer decides this. What decides it is whether the run left a SKILL.md where that agent reads, which is the same thing doctor looks at, and setup writes its own copy when it did not. Anything skills.sh really did install is left exactly as it installed it, symlinks included.
Everything else about the wiring was fine, so this cost you the skill and
nothing more. Approvals, hooks and the instructions in ~/.codex/AGENTS.md
were all in place.
A skill that will not install no longer stops setup
The skill is the one piece of the wiring nothing else depends on. It used to
be able to abort setup partway, leaving an agent half configured. Now it warns
and setup finishes the rest, and pushary doctor tells you the skill is
missing.
0.72.0
Setup tells you when Codex was already quarantined
0.71.0 stopped setup from getting your Codex binary deleted. This handles the
machines where it already happened. What macOS leaves behind passes every check
setup made: the codex on your PATH is a small JavaScript launcher and it
survives, which codex still answers, only the binary it launches is gone. So
setup wired up an agent that could not start and reported success.
It now says so before writing anything, and only when it can prove it: a vendor
directory that exists and holds nothing the size of a native binary. A layout it
does not recognise stays quiet, because telling somebody their working install is
broken is the worse mistake. Reinstall with npm install -g @openai/codex or
brew install --cask codex.
Setup no longer runs the Hermes binary either
The same hazard, one agent over. When the config edit that enables the plugin
failed, setup fell back to running hermes plugins enable pushary — executing a
third-party agent binary, which is exactly what cost people their Codex install.
The fallback also did not work, because hermes plugins enable does not see a
pip install. It is gone, and a failed config edit now tells you the one line to
add by hand.
Executing an agent binary is now a build failure rather than a habit: every
source file in this package is scanned on every CI run, and the check knows the
difference between running codex and asking which where it is.
0.71.0
Setting up Codex no longer gets Codex deleted by macOS
Setup ran codex --version to decide whether your Codex was new enough for
native hooks. On macOS, executing a binary hands it to XProtect, and current
definitions false-positive on the Codex CLI: macOS killed it and moved it to the
Trash. Reading a version number destroyed the tool it was asking, mid-setup, and
the only thing you saw was "codex was not opened because it contains malware".
Setup no longer runs Codex, or any other agent's binary, to learn something about
it. The version now comes from the package manifest next to the binary, which
covers npm, bun, pnpm and yarn installs, and from brew list for the Homebrew
cask, which ships no manifest. Same result, nothing launched.
0.70.0
A key you name is the key you get
Passing --key with a malformed value did not fail. Setup read it as no key at
all, went looking elsewhere, and used whichever key it found: one already stored
on the machine, one in your environment, or a brand new one it created by pairing.
You named a credential and quietly got a different one. It now stops and says the
key is not valid.
setup --json no longer stops to ask a question nobody can see
Asking for machine-readable output says plainly that no human is watching, but setup could still reach an agent-selection prompt. Piped into a script it waited for a keypress that was never coming, and the prompt drew itself onto the same output a program was trying to read as JSON. It now selects the agents it detects, which is exactly what pressing Enter on that prompt would have chosen.
--json also promises a single object of output, and every failing exit used to
print nothing at all. A refused key, a cancelled pairing and a crash were
indistinguishable to anything reading the output. Each now says which it was.
Setup checks its own work
An agent installer that finished without an error was reported as configured, even if nothing had been written. Doctor would then contradict setup minutes later. Setup now confirms the configuration is really there, using the same check doctor uses, so the two cannot disagree.
connect points at the fix that matches your situation
pushary connect --app gave one answer for every way a phone can be out of
reach: open the app and allow notifications. That is right when the app is signed
in and the permission was declined, and wrong otherwise. A terminal that was
never paired needs setup. A phone signed in to a colleague's account needs your
account, not a permission toggle. It now says which applies, and when it cannot
tell the two apart it says both rather than sounding certain about the wrong one.
The editor gates say when a proxy is in the way
The Cursor and VS Code gates cannot route through a corporate proxy. They still hand the decision safely to the editor's own prompt, so nothing is blocked, but every approval quietly stopped reaching your phone with no explanation. When a proxy is configured they now name it as the likely cause, and on Node 24 or newer they name the setting that fixes it.
VS Code is also now listed among the supported agents, which it has been since its plugin shipped.
0.69.0
An approval is never granted on a state Pushary could not confirm
When the hook could not reach Pushary, it read that as "you have not been stopped, and you are under no agreed scope". Those are answers, and not reaching the server is not an answer. A rule you had set to approve automatically could therefore fire during an outage on exactly the kind of call a live scope would have held back.
It now tells the two apart. If the last successful check was under a minute ago, that answer still stands, so a brief network blip changes nothing, and a stop you issued moments earlier still stops the agent. Past that, an automatic approval is withheld and the decision goes to your agent's own prompt instead. Read only shell commands are unaffected, because that list is decided on your machine and needs no server at all.
A rejected key is treated as a real answer rather than a blip, so a key you revoked stops being honoured at once.
A scope you agreed to is now honoured everywhere, not just in one place
Scope was checked when Claude Code asked before running a tool, and skipped on four other paths that could reach the same decision: Claude's permission dialog, Codex, Gemini CLI, and the remote wrapper. Each of them fetched the scope you had agreed to and then ignored it, so the same edit could be waved through depending only on which route it arrived by. All five now run the same check in the same order.
Codex patches are judged on every file they touch
A Codex patch that changes several files at once was judged on the patch as a whole, so a rule you wrote for one file did not apply when that file was part of a larger change. Every file is looked at now, and the strictest rule wins. Rules written as paths also work for Codex and Gemini, which they quietly did not before.
Command details that could contain a secret no longer leave your machine
A command like GH_TOKEN=... gh pr create carries its credential in the first
few words, and those words were being sent and stored as the label for what the
agent wanted to do. Notification text was not being cleaned at all. Both are
cleaned now, on your machine and again on arrival, and the Cursor and VS Code
gates clean the command before it is sent rather than after.
setup --dry-run really does change nothing
On a machine with no key, a dry run reached the pairing step, drew a QR code, waited for your phone, created a real API key, and then said nothing had been written. It now stops before anything is created and tells you a real run would connect a key first.
clean can no longer leave Cursor blocking your commands
Cursor is told to block a matched command if the Pushary gate cannot answer, and that list includes ordinary work like rebase, migrate, deploy and publish. Clean removed the gate's files before removing that instruction, so if the second step failed, Cursor was left blocking all of them on a file that no longer existed, under a message saying clean had finished. The order is reversed and checked, and if the instruction cannot be removed the files stay put, clean says so, and it exits with an error.
Pairing survives a dropped reply
If the reply carrying your key was lost in transit, the terminal reported that pairing had expired and you started again, while the key it never received stayed on your account. The key is now held until your terminal confirms it has it, so retrying finishes the pairing you already started. A terminal that cannot reach Pushary at all now says so instead of sitting under a QR code for fifteen minutes.
doctor reports on the agents you actually use
Doctor checked Claude Code whether or not you had it, so setting Pushary up for Codex alone produced a column of failures about software you had never installed. It now reports on the agents Pushary is set up for, and says plainly when it is set up for none.
Three things it used to call healthy, it no longer does. A key that lives only in a shell profile cannot be read by any hook, so an install where nothing could work was passing every check. A key saved inside Claude, Cursor or VS Code that no longer matches the one in use, which happens whenever pairing issues a new one, went unmentioned. And when doctor could not reach Pushary at all, it said everything was fine.
Its exit codes now distinguish a broken setup from having no phone connected from not being able to reach Pushary, so a script can tell what went wrong. A test question nobody answers is now withdrawn instead of sitting on your phone.
0.67.0
The VS Code agent can now ask for approval on your phone
Setup has a VS Code option. It installs a Pushary agent plugin, connects the MCP tools so the agent can notify you and ask you questions, and registers a permission gate so a risky terminal command reaches your phone before it runs. The policy behind it is the one your other agents already use, so a rule you wrote for Claude Code applies here too.
Two things about VS Code shaped how this works.
VS Code reads a hook's matcher but does not act on it, so the gate is called for every tool the agent uses, including reading a file. It therefore decides for itself, and for anything that is not a risky shell command it returns straight away without touching the disk or the network.
And a plugin folder does nothing until VS Code is told where it is. Setup adds that entry to your settings.json. That file usually has comments in it, and rewriting it as plain JSON would delete every one of them, so an existing file is edited one line at a time and left otherwise byte for byte identical. If your settings already have a plugin list with comments around it, setup does not guess: it prints the single line to paste and names the file.
Unlike Cursor, VS Code hooks cannot ask the editor to block a command on the plugin's behalf. So when Pushary cannot reach you, or cannot reach its own server, the gate hands the decision to VS Code's own approval prompt rather than letting the command through.
pushary clean removes all of it again, the settings.json entry included, and
pushary doctor checks the three things that can be silently wrong: whether the
plugin is registered at all, whether the gate script resolves, and whether your
key is linked.
0.66.0
Setup waited three minutes for a step that takes longer than three minutes
If you already had the app installed and signed in, pairing resolved in seconds and this never came up. If you did not, it could not work at all.
Scanning the QR without the app opens a page that sends you to the store. Install it, sign in, subscribe, come back, scan: that is never three minutes. The CLI had given up and cancelled the pairing long before, so a first-time user's first scan was guaranteed to fail, and the only route through was to notice the terminal had stopped waiting and run setup again.
Setup now waits as long as the code is actually valid, and after the first 45 seconds the spinner says what to do if the app is not installed yet. Ctrl-C still exits cleanly and cancels the pairing, so nothing holds you there.
The window itself is unchanged in what protects it: a pairing id is 128 bits, single use, deleted the moment it is claimed, and authorising one requires a signed-in session for the owning account and the public key you physically scanned.
0.65.0
The pairing QR now works when you scan it with your phone camera
It did not. Scanning it opened the app on "Unmatched Route", and the only way through was the in-app scanner, which parses the QR itself and never routes.
The QR encoded https://pushary.com/app/pair, and /app/* is claimed by the
app: the AASA lists it and so does the Android manifest. The OS therefore handed
the link to the app before the page could load, which was the intent. What it
handed over was the path /app/pair, and the app's pairing screen is /pair.
Nothing matched, on every build ever shipped.
The QR points at /pair now. Nothing claims it, so the browser always opens it,
and the page hands off to pushary://pair, which every shipped build already
routes correctly. That is the whole reason for the move: the Android claim is
compiled into the installed app, so no amount of server or app-store work fixes
the phones already out there, and this does, today.
/app/pair still serves the same page, so a link from an older CLI is not dead.
0.64.4
The shell you ran setup in still exported the key it replaced
0.64.3 stopped a stale export from hiding the working key on disk, and the rc file has been rewritten in place since 0.62. Neither reaches the shell you are standing in: the export there is still the old key, the environment wins over the config file at hook time, and the agent you start in that terminal a minute later talks to whichever workspace that key belongs to.
--connect app mints on every run and is the command onboarding prints, so this
is the ordinary case rather than an exotic one. Setup now says so under "Still to
do": open a new terminal, or re-export. No key is printed in that line.
0.64.3
A stale shell export hid the working key on disk
PUSHARY_API_KEY wins over ~/.pushary/config.json, which is right and
unchanged. But the environment used to win on the key's shape alone, so a
well-formed key the server had since revoked shadowed a working one in the config
file and setup exited "This key was rejected by pushary.com" with a perfectly
good key sitting on the machine.
That is the state any shell left open across a key rotation is in: the export is the old key, the file is the new one, and the terminal you happen to be typing in decides whether setup works.
The environment key is checked with the server before it is chosen now, and a rejected one falls through to the stored key rather than ending the run. It costs no extra call: the verdict is handed to the verification step that was already making it.
--connect app on a machine with no terminal named the wrong problem
A headless run that asked to pair said "No API key, and no terminal to sign in from" even with a key in the config file, because asking to pair deliberately takes the stored key off the table. It now says the flag needs a terminal for the QR, and that dropping it uses the key already there.
0.64.2
A run that gave up reported nothing, and said it stopped at the start
agent_setup_completed was wired to one of setup's fifteen exits: the last line
of a run that finished. Every other way out returned before it. A key the server
refused, a cancelled pairing, no terminal to sign in from, an installer that
threw, a Ctrl-C at the agent checkbox: all silent. The beacon was added so failed
and abandoned runs would stop being invisible, and the runs it could see were
still only the ones that already worked.
Every early exit reports now, and each says why it stopped: key-refused,
pairing-cancelled, no-key, login-rejected, no-agents-detected,
invalid-key, interrupted, crashed.
Argv-time exits stay silent, along with --help and --dry-run. A flag typo is
not an abandoned run: nothing was attempted and nothing was written. It is also
the fastest-failing path there is, and waiting on a beacon took setup --bogus
from 60ms to two seconds against an unreachable server. A dry run stays silent
for the other reason: counting it as a completed setup would be its own lie.
abandonedAt said where. It was always start, because markStep had no call
site outside its own test, so the field that was supposed to locate a failure was
a constant. It now moves through the run, per agent inside the configure loop, so
an installer that hangs is named rather than grouped.
Ctrl-C at the pairing QR reports before the process leaves. The cancel and the beacon run concurrently and the exit waits for both, so this adds nothing to the worst case a cancel already had.
The beacon authenticates with the key the run resolved rather than the one already installed. Most of these exits happen before the key is written to disk, so without that they posted as nobody, which meant the window holding the interesting failures was exactly the window that could not report.
0.64.1
Ctrl-C at the pairing QR printed nothing
The QR screen said "No app? Press Ctrl-C for the ways to finish without one", and
pressing it printed nothing at all. connectViaAppPairing handles SIGINT by
exiting the process itself, so control never returned to setup and the lines it
meant to show were unreachable on the one path that advertised them.
Setup now hands the escape to the pairing wait, which prints it inside the handler before exiting, and the line on screen names the command directly instead of telling you to interrupt the program to find out what it is.
The same two commands are printed when a pairing simply never completes, so both ways out of the QR screen say the same thing.
0.64.0
A typo on --dry-run ran a real clean
pushary clean --yes --dryrun, one missing hyphen, silently dropped the flag it
did not recognise and performed a full destructive clean: the stored key, the
Cursor plugin, every agent config, and the global package itself. It printed
"Clean complete." with the past tense throughout and exited 0. The typo most
worth catching was the one on the flag that means "change nothing", sitting next
to the flag that means "do not ask me".
clean now rejects a flag it does not know and exits 2 before removing
anything. --dry-run, --yes and -y are unchanged.
Setup reuses the key this machine already has
setup read PUSHARY_API_KEY and never ~/.pushary/config.json, so the key
pushary login had just written was invisible to it. login followed by setup
in the same shell ran a second browser login and minted a second key, and a
machine with no terminal that had been set up by login exited "no API key" with
a working key sitting on disk. The shell export made it work in a new shell,
which is what hid it.
Setup now reads the stored key, verifies it, and uses it. A key the server no longer recognises falls through to a fresh sign-in rather than dead-ending, and a server it cannot reach keeps the key rather than discarding it.
The Pushary app is the default connect path
An unflagged setup now pairs the app instead of sending you to the browser
subscribe page. --connect browser is the fallback for a machine with no app,
--connect web is the legacy page and keeps working exactly as before, and
--connect none skips the phone step like --skip-phone. auto and pwa are
accepted as input spellings.
Pairing only happens when there is a terminal to show the QR to and no usable key
already on the machine, so re-running setup to add an agent no longer asks you to
scan anything, and no longer mints a key you did not ask for. Typing
--connect app explicitly still pairs every time.
When pairing does not complete, setup now names the two ways to finish without the app rather than only inviting you to try the QR again.
0.63.2
Setup reports what happened, not only that it worked
The setup event fired only when at least one agent had been configured, so a run that configured nothing, failed, or stopped partway reported nothing at all. The only visible runs were the ones that already worked.
It now fires once per run whatever the outcome, and carries the connect mode and its result, how many agents failed, which were skipped, whether the run was non-interactive, and where a run stopped if it did.
This is the same event that already powers your activity feed, sent with your own API key to your own workspace. No repo path, hostname or agent content is included, and nothing is sent before you have a key.
0.63.1
pushary status --bogus printed a stack trace
An unknown flag on status threw where nothing was catching it, so Node printed
a raw stack trace and exited 1 instead of the clean usage error and exit 2
every other command gives. Other commands were unaffected.
0.63.0
Machines
pushary daemon now reports itself to your workspace once a minute, and
pushary status --devices lists what is there:
Machines
● macbook-pro 0.63.0 just now
○ build-server 0.62.6 4h agoonline is worked out from the last heartbeat rather than stored, so a laptop
that closes mid-session reads offline a few minutes later without anything
having to notice it left.
This is what lets a phone start a session on a machine you name, instead of on whichever one happens to answer first.
Presence is best effort throughout. A heartbeat that fails never stops the daemon
draining its queue, and status still reports your key and channels if the
machine list cannot be read. Against a server that predates the endpoint,
--devices says so rather than reporting an empty list.
Needs a database migration (0066). Until it is applied, --devices reports
that it could not read the list, and nothing else is affected.
0.62.6
clean rewrote your project's Cursor config and left your own alone
clean built the Cursor MCP path without a home directory, so it resolved
against whatever directory you ran it from. Inside a repo that has a
.cursor/mcp.json, it rewrote that committed file, stripping the Pushary entry
and reformatting the rest. Meanwhile your real ~/.cursor/mcp.json was never
touched, so the API key stayed in it.
Check git status in any project you ran clean from, and check
~/.cursor/mcp.json for a key you thought was gone.
Every file the CLI touches is now named in one place
setup, clean, doctor, logout and upgrade each derived the same paths by
hand, and they drifted. That drift caused this bug and the CODEX_HOME one
before it. All twenty-three call sites now resolve through one module, and a test
fails any command that starts spelling a path out again.
No behaviour changed anywhere else: setup's output is byte-identical across every mode before and after.
0.62.5
clean --dry-run changed things again
0.62.4 moved the dry-run guards into their own module and passed the flag in by value. That happened at module load, before the flag had been parsed from the command line, so every guard was built in live mode and stayed there. A dry run stripped the Codex key, rewrote agent configs and removed the plugin directory, while printing "would" beside each one.
The guards now read the flag when they run rather than when they are built, so there is no longer an order in which they can be created too early.
If you ran clean --dry-run on 0.62.4, treat it as though you ran a real clean:
re-run setup to reconfigure.
0.62.4
pushary clean crashed partway through on Node 24
clean threw ERR_INVALID_ARG_TYPE: The "options.force" property must be of type boolean and died mid-run, after cleaning some agents and before touching
the rest. Introduced in 0.62.0 by the --dry-run work, which started passing
force: undefined to rmSync on every call that omitted the option. Older Node
ignored that; Node 24 rejects it.
If a clean died on you, re-run it. It is safe to run repeatedly and picks up whatever is left.
The three mutation primitives behind --dry-run now live in src/cli/dry-run.ts
instead of inside the command file. Both --dry-run defects that reached users
were in those few lines, and both got past tests that could only read the source
as text, because code in bin/ runs at import and cannot be called by a test.
They are ordinary functions with ordinary tests now.
The authorize page's code input overflowed its card
The eight character slots were a fixed width, so they added up to 488px inside a 384px card and spilled out of both edges. They now share the row and shrink to fit, down to a 320px phone.
0.62.3
setup --connect app --key ... discarded your key
Pairing was checked before the supplied key, so passing --key or --key-stdin
alongside --connect app silently threw that key away and minted a new one. The
key you named stayed live on the account with nothing pointing at it. An
explicitly supplied key now wins, and pairing is only the key source when there
is no key to use.
Pairing still mints a key each time it runs, which is inherent to the protocol. When that replaces a key this machine was already using, setup now says so and names the old key, rather than leaving you to find it in the dashboard later. It does not revoke it for you: the same key may be in use on another machine.
The update banner offered downgrades
Setup compared its version to the registry with !==, so it announced an update
whenever the two merely differed. A machine running a build ahead of npm was told
Update available: 0.62.3 -> 0.62.2 and pointed at a command that would have
downgraded it. It now prompts only when the registry is genuinely ahead, using
the same comparison pushary upgrade already used. There is one implementation
of that comparison now instead of two.
A reused key skipped its own verification
Setup skipped the server key check whenever --connect app was passed, on the
assumption the key had just been minted. That is only true when pairing actually
produced it, so a key reused on an --connect app run was never verified. The
skip now follows where the key came from.
0.62.2
An ignored --dry-run made real changes
mode, wait, suggestions and status read their arguments positionally and
silently discarded any flag they did not recognise. So pushary mode push_only --dry-run dropped the flag and changed the mode for real. Those four now reject
an unknown flag and exit 2, and they do it before looking for your API key, so
a typo is reported as a typo rather than as a missing key.
The proxy support was never switched on
HTTP_PROXY and HTTPS_PROXY handling was written but never called from
anywhere, and Node's fetch ignores both without it. Behind a corporate proxy,
setup verified your key through the one code path that used it, reported
everything healthy, and every approval afterwards failed. It is now installed by
the shared HTTP layer, the MCP transport and the command dispatcher.
doctor --json woke your phone on every run
The test push was skipped only for --no-push. Running pushary doctor --json
on a schedule sent a real notification each time. It is now opt-out for a person
at a terminal and off by default for --json or a non-TTY. Pass --roundtrip to
send one deliberately.
Browser login could stall on the plan step
Choosing a plan navigated the same tab away from the authorize page, and that page was the only thing that could finish the login once the plan activated, so the terminal waited on a promise nothing was going to keep. Checkout now opens in a new tab and the page completes the login by itself. The CLI no longer claims it finishes on its own without saying the tab has to stay open.
0.62.1
pushary clean --dry-run was not a dry run
In 0.62.0 the flag ran four real operations while printing "would":
- uninstalled the global
@pushary/agent-hookspackage - rewrote your shell profile to remove the
claudealias - stripped the Pushary block from
~/.codex/AGENTS.md - stripped it from
~/.gemini/GEMINI.md
If you ran pushary clean --dry-run on 0.62.0 and the CLI then seemed to
disappear, that is why. Reinstall with npm i -g @pushary/agent-hooks, and check
your shell profile and those two files against version control if you keep them
there.
pushary upgrade could destroy ~/.claude.json
upgrade treated an unparseable config as an empty one and wrote it back, so a
truncated ~/.claude.json was replaced with nothing but the Pushary MCP entry,
losing every project, MCP server and permission rule in it. A half-written
~/.claude.json is what an interrupted write leaves behind, so this needed only
one earlier crash to line up.
A config that cannot be parsed is now left untouched, and the other agent files are still refreshed around it.
0.62.0
pushary clean could leave your API key on disk
If CODEX_HOME was set, clean looked for Codex's config.toml in ~/.codex
and never found the real one. It printed Codex config (not found), exited 0,
and left the key in the file. A user who ran clean to remove their key was told
it had worked.
The same fault ran through the rest of the Codex path. With CODEX_HOME set:
setupwrote the MCP server, the trust hashes and the notify entry to~/.codex/config.tomlwhile writinghooks.json,AGENTS.mdand the skill to the realCODEX_HOME, splitting the install across two directoriessetup --dry-runnamed the relocated path, so the preview and the real run disagreed about where the key would landdoctorread the relocated config and reported the key missinglogouttreated an exported-but-emptyCODEX_HOMEas a path relative to the current directory
If you use CODEX_HOME, run pushary clean --dry-run to see what is still
there, and check $CODEX_HOME/config.toml for a key you thought was gone.
New
pushary clean --dry-runlists everything it would remove and changes nothingpushary doctor --bundlewrites a redacted diagnostics file for a support thread. Keys, your home directory and the machine name are stripped.pushary logout --revokekills the key server-side as well as locally, so it stops working everywhere rather than only on this machine
Changed
pushary upgradenow refreshes Codex, Gemini CLI and Cursor, not only Claude Code, and names the agents it actually touched. It matters most for Codex, whose hooks are trusted by hash: a refreshed hook command without a refreshed trust entry is a hook Codex silently declines to run.pushary upgradewith no global install now names the install modes it can see and the command that updates each, instead of one generic line and exit 0pushary daemonstops on a rejected key or a closed plan and exits4(UNAUTHENTICATED). It used to treat that like a network blip, back off to a two-minute poll and sit there under anonlinebanner, so a revoked key left a daemon that looked healthy and would never launch anything again.- Ctrl-C during app pairing now cancels the pairing on the server, so a QR still on your screen stops being claimable immediately instead of staying live for the rest of its five minutes
setupprints the command surface when it finishes
0.61.0
The largest release since the CLI shipped. Setup no longer asks you to paste an API key, the key is checked against the server before anything is written to your machine, and commands exit with codes that mean something.
Read this first: exit codes changed
Commands that used to print a failure and exit 0 now exit non-zero. If you run
any of these in CI or chained behind &&, this is the part that affects you.
| Command | Was | Now |
|---|---|---|
any unknown command or pushary typo | 0, printed help | 2 |
doctor with a failing check | 0 | 5 |
setup with a revoked key | 0 | 4 |
setup with an expired or locked plan | 0 | 5 |
mode, wait with a rejected key | 0 | non-zero |
status with no key configured | did not exist | 3 |
The full ladder: 0 ok, 1 failed, 2 usage, 3 not configured,
4 unauthenticated, 5 problems found, 6 no device, 7 input required,
8 unreachable, 130 interrupted.
pushary doctor && deploy used to succeed against a machine doctor had just
declared broken. That is the change worth knowing about.
Sign in instead of pasting a key
pushary setup opens a browser, shows you a code, and receives the key sealed to
a keypair generated on your machine. The key is never pasted, never shown in a
terminal, and never transits in a form the server could read.
pushary loginandpushary logoutas standalone commands- Pasting a key still works, as a fallback and via
--key --key-stdinreads the key from stdin, so it stays out of your shell history and out of the process list
New commands
pushary statusreports what this machine is connected to, with--jsonpushary login,pushary logout,pushary connect
Setup
- The key is verified against the server before a single file is written. A revoked key or a locked plan stops the run instead of producing a config that looks finished and never delivers.
--yesruns setup with no prompts, for CI and images--agents claude_code,codex,...picks agents without asking--dry-runprints what it would do and touches nothing- Agent detection is real, so an agent you do not have is reported as skipped rather than counted as configured
- Ctrl-C in a trailing prompt no longer reports the whole run as failed
- A stale
PUSHARY_API_KEYexport in your shell profile is now replaced rather than skipped. It used to win forever over a newly rotated key.
Delivery is proved to your phone, not just to the workspace
Setup and doctor now check that an approval reaches you, not merely that somebody in the workspace has a device. On a shared site where the only reachable channel cannot be attributed to you, that is said plainly rather than reported as success.
Diagnostics
pushary doctor --jsonfor one machine-readable envelopepushary doctor --roundtripsends a real approval and waits for the tap. It works without a TTY, where the old confirm prompt could not run at all.pushary doctor --no-pushfor a check that stays silent- doctor reads the same key the hooks will actually use, honoring
PUSHARY_CONFIG_FILE, and reports a Claude or Cursor config holding a different key
Correctness
--helpand--versionprint and exit instead of running the command.pushary daemon --helpused to start a daemon;pushary hook --helpused to block forever on stdin.- Unknown flags are rejected on the commands that parse them, instead of being
coerced into a plausible wrong value.
--connectwith an unrecognized mode used to silently meanweb. - Every file this CLI writes is written atomically, so an interrupted setup can
no longer truncate a large
~/.claude.json - An unparseable agent config is left alone instead of being overwritten
- Files holding your key are created
0600 CODEX_HOMEis honored when locatingconfig.toml- Hermes setup works on Windows
- The Cursor plugin is staged and verified before the old one is replaced
installGloballypins the exact running version instead of drifting to latestpushary cleanremoves only the export line it wrote, not every line in your shell profile that mentions the key variable- Requests honor
HTTP_PROXYandHTTPS_PROXY - Color honors
NO_COLORandFORCE_COLOR, and turns itself off when output is not a terminal --jsonoutput is machine-readable on stdout, with human text on stderr, so a pipe never receives a spinner
Pairing
The pairing QR now points at a real web page, so scanning it on a phone without the app installed lands on install instructions for that platform instead of a dead custom-scheme link.
Known gaps
status,clean,mode,wait,stats,suggestionsandupgradestill ignore an unknown flag rather than exiting2. Unchanged from 0.60.0.pushary upgradereapplies Claude settings only. Other agents needsetup.- Codex hook trust still uses a fixed version ceiling, so a newer Codex is sent
to the manual
/hooksstep.