chadtmiller.com/knotless
git that lives in your Atmosphere account
Errors on the pages name their controls, never a command
MergedThis pull replaces the commands written into refusal text with a typed Fix list. Each client says that list in its own words: command line, MCP tools, or page controls. I read fix.rs, approvals.rs, forge.rs, landing.rs, queue.rs, auto_merge.rs, org.rs, the runner's queue ejection, the serve and MCP plumbing, and format.ts. I did not read the remaining files (session/space/cli/manage, docs, the other tests, the iOS and Svelte one-liners). The visible checks pass: check, web, docs, shots, ios and lexicons. test and e2e were still running when I looked, so I could not see them pass.
What I found:
fix::saidfinds aRefusedin an error chain withdowncast_refand swaps its CLI text for the target wording inside the{err:#}string. That holds as long as the refusal'sDisplayis unchanged, and the unit tests cover it as both context and cause.landing::decidenow mergesRefusedvalues (why; and not) and joins their fixes before saying them once in the caller's words. Both the MCP check and the pages'getMergeStatusread sensibly this way.- Removing
in_toolsand routing everything throughfailedis cleaner than the old string search.
Things I judged acceptable and did not hold the pull for:
- Pages say nothing for
Force,WaitandLogs, as the body states. pull showno longer prints the approve command for a pull ejected for its reviews, which the body also names.- The
ejectedWhyregex informat.tsis anchored on the old runner text, so it only strips older records.
The only change since my approval is one MCP test assertion. It now expects the refusal in tool words (delegates its writes with the account_delegate tool) where it expected the old CLI command. That matches the pull's design, so my earlier approval stands. I did not run the test.
The only change since my approval is one line in web/orgs.rs. The organization leave warning is now passed to crate::shown by reference (warning.as_ref().map(crate::shown)) instead of being moved. Behaviour is unchanged, so my earlier approval stands. I did not build or run it.
8 passed, 2 skipped
What changes
What to do, typed. A refusal no longer writes a command into its text.
knotless::fixhasRefused { why, fixes }, where eachFix(approve, force, ready, merge, queue, use the queue, add a runner, rerun, wait, logs, define a label, delegate, delegate an organization, join in public, show a membership, log in) is said in one of threeWords:Clias a command (theDisplay, so the command line, logs and git clients read what they did),Toolsas an MCP tool,Pagesas the control on the pages, or not at all where they have none (--force,--wait, reading logs beside the failed-check links).fix::said(err, words)says an error in those words wherever theRefusedsits in its chain, context or cause.Every refusal that reached a page. These now return
Refused: the reviews refusal (approvals::refusal), the merge gate (forge::refusal), a draft, a queue-only branch with no runners, queueing and auto-merge with no runners or no queue, a failed pre-flight on queueing, an undefined label, sharing for writing undelegated, rerunning with no runners, an organization with no gateway, a hidden join with no space, a hidden member maintaining (org::needs_public, nowResult<(), Refused>), and not logged in (session::ensure,space::joined,space::delegation). Two organization refusals the gateway raises, which reach the pages as text, name no command at all now.org::leaveanswers its warning as the error, said by each client.Serve says them in page words.
crate::shownis what every page-facing failure goes through:AppError::upstream,input,internal, therefusedofweb/repos.rs,web/threads.rsandweb/account_settings.rs,not_found, and an organization leave's warning.getMergeStatus.reasonis said in page words too (landing::decidetakesWords;mergeabletakes them,Cliforpull merge --checkand gate refusals,Toolsfor MCP).MCP.
in_tools, which found the reviews refusal by its text, is gone:failedsays every error in tool words, andpull_merge'scheckanswers its reason so.The runner's ejections. Ejecting a pull not reviewed as its branch asks, the runner records only what it lacks and how long it waited, no command. Older records keep the command:
ejectedWhyon the pages andStanding.ejectedin the app strip it, as they did the logs command.Pages. The transfer offer's hint no longer names
knotless repo accept.Why
Error messages shown on the pages named CLI commands (
approves it with `knotless comment add … --approve`; … merges past it with `--force`), which nobody on a page can run. db8eed1d stripped one sentence from ejection reasons; the rest came from the knotless crate's refusals, written once for the command line and passed through as the pages' error. Rewriting text at each reader was how MCP handled the one it knew of; a typed fix makes every client say every refusal its own way, and the test over everyFixholds the pages to naming no command.Wordings that read the same everywhere (
ask one of its admins to invite you,join it first) carry no fix: the gateway raises them, and they cross the wire as text.How it was checked
cargo checkof the five crates. Unit tests infix.rs: everyFixin page words names no command or flag, and what the pages cannot do is left out;saidfinds a refusal as cause or as context. The tests on wordings that moved are updated (forge.rs,gateway/orgs.rs, the runner's gate ejection now asserting no command in the reason, the pages' and private flow's share refusal asserting the control is named and no command, the e2e flow's alert, the app's model check). The rest is left to CI.Shortcomings
knotless pull showno longer prints how to approve a pull ejected for its reviews; it lists the reviews, andpull mergesays how when it refuses. Messages only the command line prints (orgnotes,login, secrets,upgrade) still name commands, as they should. The pages have no control to merge past checks or reviews, so the owner's force is said nowhere there.