Repository navigation
v0.63.0
Added
-
ops:grep=PATTERN— filter the op roster by name, syntax or description instead of piping it throughgrep(#1318).supertool 'ops'prints every op's signature, and finding the one op that does X meant reading all of it or piping it throughgrep— the exact motion the shipped raw-command guard (supertool-no-cut.md) blocks, correctly, leaving a redirect-to-a-temp-file workaround as the only route left. Three independent agents hit it in one week.ops:grep=PATTERNmatches op name, syntax and description — including a dispatchable op with no.supertool.jsonentry, soops:grep=introductionfinds it rather than reporting0 of Mfor an op that plainly exists — and prints only the matching rows, always statingN of M ops matched—0 of Mincluded — so an empty result can never be misread as a short roster. Case-sensitive, like every other pattern slot in this file. A pattern containing:(the shape a search forread:PATH-style syntax naturally has) reaches the filter whole rather than being silently cut at the first colon by the CLI tokenizer; an empty config falls back to a bare name list rather than "0 of 0". Reuses_pattern_gate, the single chokepoint every other pattern-taking op goes through (#2574 did the same forbetween), withcheck_saturation=Falsesince the count line already discloses when a pattern matches everything. Scoped to bareops, the same wayops:roster/ops:full/ops:sessionare —ops-compact:grep=Xstill refuses, naming the new token. -
Added
oss-tick, a new shipped preset (presets/oss). It composes the mechanical opening of theclaude-ossmaintainer tick -- last state entry, pending wait, plugin identity vs last recorded,git fetch && git pull --ff-only, the board (gh-prs/gh-issues/gh-branch/git-worktrees) and radar tier resolution -- into one round-trip receipt whose every row is one of three states, never two. It is a thin shim: it locates the installedossplugin from Claude Code's own install record and execs that plugin's ownoss_state.py, so no maintainer-loop judgment lives in this repository. (#1985) -
youtube_reply,youtube_like,youtube_status_since— the rest of #227's YouTube write ops (#2593).youtube_reply:COMMENT_ID|TEXT_OR_file://PATH[|force][|force-dup]posts a reply viacomments.insertand reads it back the same three-verdict way (verified/MISMATCH/could-not-verify)youtube_commentdoes, keyed for its duplicate guard on the parent comment id rather than the video so a second reply to a different comment on the same video is not wrongly refused.youtube_like:VIDEO_ID_OR_URL[|force][|force-dup]sets this account's rating to "like" viavideos.rateand confirms it withvideos.getRating, sharingyoutube_comment's plainVIDEO_IDduplicate key on purpose (a conservative default, not a claim the two actions are the same).youtube_status_since[:ISO]lists new top-level comments on this account's own videos since a cutoff (default 24 hours), needing the same OAuth2 grant as the write ops because "own videos" is only answerable for the authorised account, but never writing, so it carries no sentinel, no rate cap and no confirmation gate. All three reuse_oauth,_yt.authorized,_sentineland_publish_safetyunchanged, per #227's own note that they were "argv and an endpoint each" once the write machinery was proved out byyoutube_comment(#2543).
Fixed
-
gl-pipeline:ID:fullandmcp_stop:--allare now declared where callers read them (#1315). Both tokens already worked --gl-pipeline's_FILTERSacceptedfullandmcp_stop's underlying script treated--allidentically to the dedicatedmcp_stop_allop -- but neithersyntaxstring nor description named them, so a caller who guessed either spelling got a working call the registry never told them about.gl-pipeline'ssyntaxand refusal text now namefullexplicitly;mcp_stop's description now says--allreaches the same code path asmcp_stop_all, which stays the canonical spelling. -
grep's0 resultsoutput now names case-insensitivity when the pattern DOES match case-insensitively, so a write confirmed with a differently-capitalised grep no longer reads as a failed write (#1777). -
read:PATH:OFFSET:LIMIT's disclosure of which form was read -- and how to get the other one -- is now also repeated at the foot of a long window, not only above the content, so it does not scroll off before the caller reaches the lines it corrects (#1777). -
Corrected the last two stale
ops/ops-compact/ops:fullbyte-count claims #1877 could not reach, plus five more stale_HOOK_OUTPUT_CAP_BYTESmentions self-review found in the same sweep (#1887). Two of the four sites #1877 handed back asreport-for-filinghad already been fixed by unrelated, later commits (_supertool.py:2575by anop_ops()docstring rewrite,_supertool.py:17101's74,838figure by a different correction) before this lane reached them, anddocs/contributing.md:122's figures already matched the render — except one clause in the same paragraph nobody had checked, which still said_HOOK_OUTPUT_CAP_BYTES=7,168(#2029 corrected the constant to 10,000 in 2026-08-27 and missed this exact sentence). The one live instance among the four named sites,tests/test_ops_roster_1231.py's module docstring, still stated the pre-#1774 pair (ops47,254 bytes,ops-compact9,067, cap ~7,168) as a present-tense fact; it is now a dated correction naming the actual figures (ops4,710 bytes,ops-compact18,510, cap 10,000). Self-review then found the identical stale7,168/~7KBliteral surviving in four more placestests/test_render_size_claims_1877.py's registry never reaches because they are.pyprose rather than a graded doc claim:_supertool.py'sop_ops()docstring and_ROSTER_LEGEND(the latter rendered live in everyops/ops:rostercall),tests/test_description_is_not_a_changelog_1774.py, andtests/test_guard_scope_is_a_route_not_a_file_1671.py— whose_SESSION_CAP = 7168module constant now derives fromsupertool._HOOK_OUTPUT_CAP_BYTESinstead of copying the literal a second time.tests/test_render_size_claims_1877.py'sUNCOVEREDregistry, which named the three #1877 sites as ungraded gaps, is now empty. -
slack_authorizationno longer prints a raw config-file key at column 0 (#2430).presets/slack/auth.pyreadchannel_id-- a key straight out of~/.config/supertool/slack_authorization.json-- and printed it unflattened at the start of a line in both_list_all()(the bare listing) and_one()(the per-channel header). A key carrying an embedded newline could render as two physical lines, the second indistinguishable from a genuine channel row -- the exact rendering shape_supertool.pyalready flattens for an"ops"/"aliases"config key via_flat_field(..., disclose_newline=True). Both sites now routechannel_idthrough the preset-side equivalent,_untrusted.flat(channel_id, disclose_newline=True), with a new regression test pinning that a newline in the key cannot forge an extra row, paired with a positive control confirming two genuinely distinct channels still render as two separate rows. -
_toml_delimiter_hint()stayed silent on a'''embedded in prose that closes a value at the END of its own line ("...closes like this: '''"), rather than immediately before more text on the same line. The trailing garbage that early close produced landed on the NEXT line, past where the #1830 same-line check ever looked, so the payload raised a bare TOML parse error with no delimiter hint at all -- the shape the hint exists to explain, unexplained._toml_delimiter_early_close()now walks past blank/comment lines to check whether what follows looks like a real next statement (#2545). -
_lock_dir_usable's write probe no longer falls back with no lock held on a one-off, transient failure (#2563). Its own docstring named a transient Windows AV scan on the just-created probe file as a case it could hit, but the function had only one failure path -- anyOSErroron the probe returnedFalseimmediately, andserialize_oncetreated that the same aslock_dirbeing genuinely, permanently unusable, runningfn()with no lock held. The probe filename is unique per call (uuid4), so contention can never trigger this; only a one-off hiccup or a genuinely broken directory can. It now retries the probe once, against a fresh probe name, after a brief pause before concludinglock_diris unusable -- negligible against the multi-minutetimeout_sthis whole probe exists to avoid spending on a false positive. -
The token-file mode-bit probe (
_publish_safety._mode_bits_are_enforced) is now created next to the credential it is judging rather than inTMPDIR-- a probe on a different mount than the credential (exFAT/FAT/SMB, a container/tmp) could take the softWARNINGarm for a genuinely group/world-readable credential that lived on an enforcing filesystem. A credential directory that is missing or not writable falls back to the old behaviour, reported on stderr rather than silently (#2597). -
KNOWN_RESIDUEintests/test_markdownlint_noise_2012.pyis now keyed by(file, rule_id)instead of by file alone (#2598). A per-file exemption made a second, unrelated markdownlint rule landing in an already-exempted file invisible to the residue test -- only the repo-wideFINDINGS_CEILINGwould ever have caught it, with 13 findings of headroom._lint_all_tracked_md_with_rules()now keeps each finding's own rule code, and_residue_violations()checks per (file, rule) pair. -
youtube_commentandyoutube_authno longer print raw OAuth/API error bodies or the cached scope string.presets/youtube/comment.py's twoget_access_token()/insert-timeexceptarms andpresets/youtube/auth.py'sauthorize()error arm wrotesys.stderr.write(f"ERROR: {e}\n")straight from a Google-supplied body, andauth.py'syoutube_auth:statusprinted the cachedscopefield the same unescaped way -- a newline anywhere in either would have put the remainder at column 0 of the receipt, exactly the shape #227 already fixed at one sibling site (comment.py:140'sverify()). All four now go throughrepr(safe_short(str(e), 300)), matching that fixed site (#2599). -
youtube_commentsupports:::as an unambiguous field separator, alongside|.parse_args's trailing-field scan for|force/|force-dupcannot tell "the operator's last pipe-delimited segment is body text" from "the operator meant the flag" once both spell the same word --youtube_comment:VID|may the|forcesilently dropped "force" from the body and forged the publish confirmation, on the one guard standing between a call and a public, un-deletable comment.:::is not a character ordinary prose would contain by accident, so opting into it (VIDEO:::body ending in the word force:::force) keeps a body's own trailingforce/force-dup-looking text intact instead of guessing. Default|parsing is unchanged, since redefining it would silently reinterpret every already-shipped|forcecall site (#2600). -
which_excluding_cwd()'s three copies (validators/common/spawnable.py,presets/_spawnable.py,_supertool.py) reported a genuinely installed tool as absent whenever the caller'sPATHwas unset (a strippedenv=on a parent spawn, some cron/launchd contexts) rather than deliberately empty.shutil.which()itself falls back toos.confstr("CS_PATH")/os.defpathwhenPATHis unset, and only refuses on an explicitly emptyPATH=""(bpo-35755); all three copies now mirror that distinction instead of collapsing both cases (#2603). -
The two bare-spawn registers (
tests/test_gate_matches_spawn_2579.py,tests/test_spawnable_register_2540.py) now catch what their own assertion text already claimed to cover (#2606)._bare_existence_check_without_dirname_guard()previously missed a bare-importedisfile(X)spelling and an existence check with no surroundingand-chain at all, and false-flagged adirname()guard nested insidebool(...); it now reasons over the whole enclosing AND/OR expression by truth table rather than searching a single BoolOp's direct values, sodirname(X) or Path(X).exists()is caught as unguarded (anorneeds only one operand true) whiledirname(X) and (Y or (Z and Path(X).exists()))is correctly left alone -- and correlates the checked name against what a chokepoint call (spawnable()/argv0()/resolve_bin_cmd()) actually resolves, so an adapter's unrelated file-existence check (doesCargo.toml/go.modexist?) is no longer a false positive. Both registers' own assertion text now states the scope it does not cover -- a bare, entirely ungated argv[0] literal istests/test_bare_argv0_construction_2605.py's job instead. -
The non-adapter bare-spawn register (
tests/test_bare_spawn_cwd_gate_2596.py) now walkspresets/,hooks/andnotifiers/instead of a hand-listed six-file dict (#2611). The oldSITESdict could not discover a seventh site by construction; walking the real directories found three previously invisible instances in_supertool.pyitself --_has_rtk(),_has_ctags()and_doctor_symlink()each cached a rawshutil.which()result and spawned it, the same current-directory-search shape #2596/#2575 fixed elsewhere -- now routed through this file's own_which_excluding_cwd().hooks/guard-selftest.py's own instance of the same class is tracked separately as #2610 and carried in the register'sALLOWLISTrather than silently unwalked. -
watches/radar's desktop-notification disclosure now distinguishes "will fire" from "opted in but it won't" -- previously it read ON wheneverSUPERTOOL_WATCH_DESKTOPwas set, even whendesktop_notifyitself would silently no-op because the platform has noosascriptequivalent, or becauseosascriptwas absent or excluded by the cwd guard (#2612). -
which_excluding_cwd()'s docstring (validators/common/spawnable.py,presets/_spawnable.py) overclaimed that anynamewith a directory component "carries none of" the cwd-shadowing risk. True only for an absolute component -- a relative one (./bin,tools/x) still resolves against cwd, which is deliberate and already config-gated (a validator'senvmay point at a relative binary path), not an oversight. Reworded to say so explicitly (#2613). -
test_the_environment_switch_turns_it_off(tests/test_read_elide_unchanged_1329.py) is no longer order-dependent under xdist (#2619). It failed once on master (ubuntu-latest/3.11,popen-gw3) on the baseline elision check -- content came back on a second, still-unchanged read, before the environment switch was even touched. No sibling-test state leak could be found; instead the test's twoop_readcalls used realtime.time(), unlike this file's other tests exercising the same "read twice, expect elision" shape, already frozen for the identical class of race in #2354. Reproduced directly:_read_elide's window guard is0 <= now - prior[1] <= window-- deliberately conservative, so it declines to elide on a negative gap rather than guessing -- and a backward step of the wall clock as small as 50ms between the two calls (an NTP correction or VM clock skew, the same "loaded parallel CI runner" shape #2354 already names) makes the gap negative and the elision is correctly refused. Time is now frozen in this test, matching its siblings, and in two more tests in the same file found to have the identical unfrozen back-to-back-op_readshape (test_second_identical_read_is_elided_and_says_how_to_undo_it,test_full_never_elides_and_is_named_by_the_elision). A new positive control (test_a_backward_clock_step_still_correctly_declines_to_elide) pins the0 <=half of the guard, which nothing exercised before. -
_sanitize.py::wrap()and each preset's ownread.py::render()(youtube, bluesky, devto, hashnode) now flatten an injection-scan hit -- both a raw newline and a raw carriage return -- before joining it into the printedPOSSIBLE INJECTIONwarning line.detect()returns the matched substring itself, and one of its own patterns ((?:^|\n)\s*system\s*:) can match across an embedded newline (with an optional carriage return right after it) in untrusted text -- unflattened, that put attacker-chosen text at the start of a second line, or moved the cursor back to column 0 of the current one, indistinguishable from a line the tool wrote itself. #2593 shipped the same one-line fix at one call site (presets/youtube/status_since.py, on the still-unmergedfix/2593branch / PR #2635); this closes the 8 remaining call sites across all 4 preset directories, includingpresets/hashnode/read.py, which the issue's own recon pass had not checked but carries the identical shape (#2636). -
presets/oss/shim.pyandpresets/oss/tick.pynow carry the exception's own reason through the three except-arms that previously discarded it:_load_record'sexcept (OSError, ValueError): return {},_install_roots'sexcept OSError: return [], andtick.state_file'sexcept OSError: return None, "{} not found". A genuine read/parse failure -- an unreadable or corrupt install record, a permission-denied cache root, a.oss.local.jsonthat exists but cannot be read -- rendered identically to "legitimately not installed"/"legitimately not resolved"/"legitimately not found", which sent a maintainer whose plugin is correctly installed downtick.py's reinstall-itNEXT:line (#2638). -
oss-tick's pending-wait guard (presets/oss/tick.py::compose) gated dispatch withrows["pending_wait"].startswith("holds"), matched against the installedossplugin's ownoss_state.py --pending-waitoutput -- but that script prints a holding or could-not-evaluate wait as a JSON object (json.dumps(record, indent=2)), which starts with{, never with the literal wordholds. A real holding wait therefore fell through to"proceed to dispatch"instead of the do-not-dispatchNEXT:line.presets/oss/tick.pynow parses the script's real shapes via two new helpers,_parse_pending_waitand_parse_plugin_identity_check-- the latter for a sibling instance of the identical root cause in theplugin identity vs last recordedrow, where the script's one-line stderr receipt is merged ahead of its own JSON record on stdout by this shim's_run, and used to render raw JSON into that row's line rather than the human-readable sentence the row's own value is supposed to carry.docs/presets/oss.md's vocabulary table gains theroute-mismatchstate this fix now surfaces, andtests/test_oss_preset_1985.py's fixtures, which faked bare-word output the real script never produces, are corrected to its actual shapes (#2639).
Security
argv0()/_spawnable()no longer hand a refused cwd-only match to the OS's own search (#2578). Both chokepoints endedwhich_excluding_cwd(name) or name: when a match existed only via the current directory,which_excluding_cwd()correctly refused it, but the fallback then handedsubprocessthe bare name -- and on Windows,CreateProcessperforms its own PATH+cwd search, resolving the very match this module exists to refuse. A genuinely absent tool (no match anywhere, not even cwd) is unaffected and still returns the bare name unchanged. Also routeshooks/guard-selftest.py's bash resolution through the same chokepoint (#2610), closing the same class in a pre-existing, out-of-band diagnostic that predates the audited range.