Repository navigation
Conflicted Sync: upstream/develop 2026-09-11 (95 commits) - #167
Merged
Merged
Conversation
Auto-submit updated manifests to microsoft/winget-pkgs on stable releases using vedantmgoyal9/winget-releaser. Windows users can now install via `winget install rtk-ai.rtk`. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move placement to make winget command easier to find Add winget commands into other documentation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Next Release
…master--components--rtk chore(master): release 0.43.0
Next Release
…master--components--rtk chore(master): release 0.44.0
Next Release
…master--components--rtk chore(master): release 0.44.1
Next Release
…master--components--rtk chore(master): release 0.44.2
Next Release
…master--components--rtk chore(master): release 0.45.0
Next Release
…master--components--rtk chore(master): release 0.46.0
Next Release
Next Release
…master--components--rtk chore(master): release 0.47.0
`filter_migrate_status` looked for whitespace after the `202...` id and fell
back to a hardcoded 20 bytes when there was none -- so an id that runs to the
end of the line slices past it:
Migration could not be applied at 2024-01-01T12:00:00
len 53, pos 34, end 20 -> line[34..54] -> end byte index 54 is out of bounds
Release builds set `panic = "abort"`, so `rtk prisma migrate status` dies and
the user loses the command output entirely -- the one thing a filter must never
do. A multibyte character straddling that offset panics the same way, on the
char boundary instead.
The fallback is the rest of the line, which is what the sibling
`filter_migrate_dev` has always used 70 lines above.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Next Release
…master--components--rtk chore(master): release 0.48.0
Hermetic temp repo with objects of every relevant type (large/small UTF-8, Latin-1 and UTF-16 blobs, a tree, commits). A fixed-seed PRNG generates 600 'git show <random flags> <arg>' invocations and checks rtk's classification (windowed / passthrough / commit-diff) against 'git cat-file -t' ground truth: 0 misroutes (never window a non-blob), 0 silent losses (a large recoverable UTF-8 blob always windows), and byte-identical passthrough for declined blobs. A companion test proves the recovery hint reconstructs a windowed blob byte-for-byte.
…r-candidate probe The blob-show classifier still had a selection gap for short-flag CLUSTERS. git re-parses a cluster's tail (`-wG x:y` == `-w -G x:y`), so `git show -wG x:y HEAD:big` feeds `x:y` to -G and dumps only `HEAD:big`. But `show_positionals` knew only single-letter value flags, so it kept `x:y` as the first positional; the cat-file probe rejected it and the real blob fell through to the commit-diff path — either dropping the windowing savings (large UTF-8 blob) or lossily decoding a Latin-1 blob into U+FFFD mojibake. Two changes, neither of which mirrors git's flag grammar: * Cluster-aware walker. `flag_token_consumes_next` extends the existing flag/value table to a short cluster's tail: the first value-taking short flag takes the rest of the cluster inline (`-Sfoo`) or, if it is the last char, the next token (`-wG x`). `is_short_value_flag` derives its char set FROM `consumes_next_token_as_value` so the table stays the single source of truth. (TODO points at rtk-ai#3681's ValueSpec.) * Probe every candidate, keep any blob on the byte-safe path. `run_show` now cat-file-probes EVERY `rev:path` positional, not just the first, and takes the byte-safe path whenever ANY resolves to a blob — so even a walker miss can't misroute a blob's raw bytes through the lossy commit-diff decode. Windowing itself stays gated on a single sole positional, which also keeps `git show <commit> <rev:path>` (two real objects git concatenates) as byte-identical passthrough. Also force byte-identical passthrough (never window) when a content-transforming flag (--textconv/--filters/--ext-diff) or a trailing `-- <pathspec>` is present: the `git show rev:path | tail` recovery hint omits them, so windowing could not reconstruct git's bytes exactly. Narrows the fidelity-invariant comment accordingly. `blob_show_target` (first-positional-only) is replaced by `blob_candidates` (all candidates); unit tests migrated and extended to cover the cluster shapes.
…ial fuzzer The blocker hid for three rounds because the fuzzer only ever paired a colon-carrying flag cluster (`-wG x:y`, `-pS url:1`, …) with a blob-LESS `HEAD` (the CommitMisroute trap). It never placed such a cluster BEFORE a real blob, which is exactly where the walker gap bit. Adds four crossed kinds: * ClusterLargeUtf8 — cluster + large UTF-8 blob → MUST window (0 silent loss); * ClusterLatin1 — cluster + Latin-1 blob → byte-identical to `git show <same args>`, no U+FFFD mojibake; * TextconvLargeUtf8 / PathspecLargeUtf8 — `--textconv` or a trailing `-- <path>` → never windowed, byte-identical passthrough. Plus a dedicated end-to-end test that `-wG x:y HEAD:large.txt` windows and that its recovery hint (which names only the blob arg) reconstructs the blob byte-for-byte. The extended fuzzer FAILS on the pre-fix code (verified: cluster windowing regression + Latin-1 fidelity break) and passes on the fix. Seed and iteration count unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MNqtCagEUryc1fgw62APkm
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MNqtCagEUryc1fgw62APkm
… gain --reset Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MNqtCagEUryc1fgw62APkm
The usage guard returns early when no hash and no --list is given, and --list returns before this match, so the arm could never run. Removing it keeps the usage string in one place. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
feat(recall): SQLite recall system
A non-numeric pattern stops at clap's parse error and falls back to raw grep, so rtk and grep agree byte-for-byte whether or not `-l` is bound to --max-len: the three existing cases still pass with the bug reintroduced. Only a numeric pattern reaches the wrong answer -- rtk printed nothing and exited 1 where grep listed the file. Drop the clap-level test and the comment sentence with it. Develop's test_grep_parse_files_with_matches_l already fails under the same mutation, and -L never had a short binding on --max-len, so src/main.rs is untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…collision test(grep): pin -l/-L parity with grep, including numeric patterns
`rtk rewrite`'s exit-code protocol is a public contract -- the claude and cursor shell hooks, the opencode and pi TypeScript plugins, the hermes Python adapter and openclaw all branch on it -- but no Rust test ever called `run()`. `rewrite_cmd`'s own `exit_code_protocol` module asserts against a locally re-implemented `expected_exit_code()` table, so the real mapping could change without a single failure, including the rtk-ai#1155 invariant that a `Default` verdict must exit 3 and never 0. Add an integration test that spawns the binary in a sandboxed HOME/XDG_CONFIG_HOME/CLAUDE_CONFIG_DIR with project-level permission rules, and pins the actual (exit code, stdout) pairs for allow, ask, deny, compound deny, passthrough, default, compound rewrite, fd-dup redirect, unattestable constructs and heredocs. Alongside it, pin the two decision paths against each other on one corpus. `rtk rewrite` and `rtk hook claude` answer the same question through two independently written flows; the corpus asserts they agree, and a separate test pins the one place they don't -- an already-RTK-prefixed command, which the in-process hook defers on and `rtk rewrite` reports as an ask-rewrite with the command unchanged. Modelled on registry.rs's `segmenter_consistency` module. Also pin `rtk hook check`, which had no test at all. It calls `rewrite_command` directly with no verdict and none of the hooks' gates, so it reports a rewrite for command substitutions and file redirects that both hook paths refuse to touch. No source change: this characterizes today's behavior so the decision-flow consolidation can be shown to preserve it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The winget job sat in the release workflow's critical path: cd.yml gates update-latest-tag on build-release without `if: always()`, so a failed manifest submission would fail release.yml and leave the 'latest' tag on the previous version. Mark the job continue-on-error so a submission failure cannot hold back the release. Pin winget-releaser to 4ffc7888bffd451b357355dc214d43bb9f23917e (v2) rather than the mutable major tag, matching the pinning underway across the repo. The step receives a classic PAT with write access to public repositories, so the resolved ref should not be able to move underneath it.
The frontmatter description renders as the page's meta description and search snippet, so it advertised an install method list that omitted the one the page now documents.
Re-raise SIGINT/SIGTERM once output is printed so callers still see signal death, not a plain exit code.
fix(rewrite): process wrapper prefixes
The v2 tag is the action's only tag and has not moved since 2024-11-27, while development continued on main. Four fixes sit between them, one of which matters here: the action runs cargo binstall komac, and cargo is missing on ubuntu-slim runners without it. Pin to the most recent functional commit rather than to main, whose two newer commits are documentation only. Its inputs are a superset of the tag's, so the step needs no other change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The signed record() change (9da3270) lets a net-regressing filter store a negative savings_pct, so both overall_savings_pct and avg_savings_per_command can now be negative. Two telemetry tests still asserted `(0.0..=100.0)` against the real user DB, so they panicked deterministically on any machine whose DB held a net-negative row -- which is why CI failed on all three runners after the approval, on test_enriched_stats_returns_valid_data. Widen both assertions (test_enriched_stats and its twin test_get_stats_returns_tuple) to the real invariant -- a saving never exceeds 100% -- dropping the stale 0 floor, and add a hermetic in-memory test pinning that a worsening command yields a negative aggregate instead of a fake 0.
feat(ci): add winget manifest automation
`rtk hook <agent>` and `rtk rewrite` asked the same question -- may this command be rewritten, and may the rewrite be auto-allowed? -- through two independently written copies of the same four steps, over two isomorphic enums (`HookDecision` and `RewriteOutcome`). A fix to the gate order, or a new construct to refuse, had to be made twice. rtk-ai#3704 consolidated raw-command lexing and named this duplication as its follow-up. Move the decision into `hooks::decision`. What legitimately differs between the callers stays outside it: `decide_with_params` takes both the permission verdict and the rewrite parameters, so each host consults its own rules and no test answers differently on a machine whose config.toml excludes a command (rtk-ai#3146); `decide` is the wrapper that reads config for production callers. The identity-rewrite policy is likewise applied by the caller that wants it: `decide_for_agent` is the composition every hook shares, while `decide` alone is what the `rtk rewrite` CLI renders. That policy is the one place the two paths disagree. `get_rewritten` suppressed a rewrite that changed nothing; `rewrite_cmd` had no such check and reported it as a normal rewrite. Rather than silently picking a side, the suppression is now an explicit `suppress_identity` applied at `hook_cmd`'s single seam, with the difference and its one observable consequence documented where it lives. Resolving it is a deliberate behavior change and is not part of this refactor. `get_rewritten`'s heredoc check is dropped as a duplicate rather than as the guarantee itself: `rewrite_command` refuses heredocs through its own `has_heredoc` (registry.rs:601,616), which is what the hook path was asking a second time. It was also unreachable for the common forms, since a `<<` operand reads as a file target and `contains_unattestable_construct` returns first -- the exception being `<< /dev/null`, which that gate lets past and the registry still refuses (rtk-ai#3980). `rewrite_cmd::run` is reduced to exit-code rendering and `hook_cmd`'s eight response builders are unchanged -- `HookDecision` keeps its name, so they match on the shared type without edits. `rewrite_cmd`'s unattestable-construct tests are dropped as verbatim duplicates of the shared module's that exercised nothing in that file. No behavior change. The characterization tests added in the previous commit pass unmodified, and the out-of-crate suites are unchanged from their pre-refactor baselines: hooks/claude/test-rtk-rewrite.sh 58/66 (the 8 are the pre-existing audit-log gap, `rtk rewrite` never having logged), hermes 18 passed, scripts/test-all.sh 105/13/5. `rtk rewrite "git status"` benchmarks at 6.7ms. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`rtk hook check` called `registry::rewrite_command` directly, with no permission verdict and none of the gates the hooks apply. It therefore reported a rewrite for command substitutions, file redirects and heredocs that both hook paths refuse to touch -- the diagnostic disagreed with the thing it exists to diagnose, and did so in the direction that matters, by claiming RTK would rewrite a command it deliberately leaves alone. Route it through `hooks::decision` so it answers the same question, and report a deny rule distinctly from "no rewrite" rather than collapsing both into one message. Both still exit 1. That makes the answer agent-dependent, so `--agent` stops being discarded. `AgentPath` records what actually differs between agents, which is whose permission rules their hook consults: the six that decide in-process via `rtk hook <agent>` use their own host's rules, the five whose plugin shells out to `rtk rewrite` get Claude's (that entry point cannot be told who is asking), and the six that install only a rules file have no hook and so no rules at all. Every install target resolves -- including `codex` and `openclaw`, which are install flags rather than `AgentTarget` variants -- and only a genuine typo is rejected. What does *not* differ is a rewrite that changed nothing: every agent discards it, the in-process hosts in `hook_cmd` and the others in their own plugin, since `hooks/opencode/rtk.ts`, `hooks/pi/rtk.ts` (shared with omp) and hermes' `__init__.py` all gate on `rewritten != command`. `AgentPath` suppresses it for every variant. Only the bare `rtk rewrite` CLI reports the no-op, and no agent consumes that answer raw. Consulting no rules and ignoring `--agent` is what made the diagnostic contradict every host: under a Claude deny rule for `git status` it reported `rtk git status` while `rtk hook claude` refused the command outright; it reported a rewrite for `rtk git status`, which no agent applies; and since `--agent` selected nothing, the answer described no host in particular. The expectations pinned in the characterization commit are updated here, in the same commit, so the behavior change is visible as a diff rather than as a test that quietly stopped asserting. That harness now also asserts the hook exits 0 and never panics: a crash produces empty stdout, which would let every "expect no output" assertion pass vacuously. The agent list is derived from `AgentTarget::value_variants()` so a new variant fails the test instead of silently becoming unanswerable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…lidation refactor(hooks): consolidate the hook decision behind one shared decide
`tokens_saved_24h` and `total_tokens_saved` both return `i64` from an unclamped `SUM(saved_tokens)` -- deliberately, unlike the `as usize` readers that take `.max(0)` for their unsigned API. A window whose filters emitted more than they saved sums honestly negative, so `assert!(saved_24h >= 0)` panics on any DB holding a net-negative row. No ordering invariant replaces the floor: with a positive row inside the 24h window and a larger negative one outside it, saved_24h (90) exceeds saved_total (-110). Nothing about either value is assertable, so the test stops binding them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…-blob-preview feat(git): filter large git show blob dumps with recovery and Latin-1 decoding
test(diff): pin the shape detectors, the structural index lines and the GNU roots
fix(prisma): stop migrate status panicking on a trailing migration id
…2026-09-11 One conflict, src/hooks/hook_cmd.rs: upstream rtk-ai#3955 moved the hook decision into src/hooks/decision.rs with suppress_identity; the fork's already-rtk Allow/Ask assertion arms are a competing opinion on the same case and go, with their dead helpers, renderer guards and tests. The rtk-aware matcher and the already-rtk Deny assertion (rtk-ai#3195 adoption) are additive and stay. Writeup: claudedocs/sync-conflict-2026-09-11.md Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Conflicted Sync of
upstream/develop(headfc19d7a, tagdev-0.50.0-rc.427, 95 commits since the 2026-09-07 sync) into forkdevelop, per the CONTEXT.md Conflicted Sync policy: topic branch, upstream wins, only proven-additive fork code survives.Full writeup:
claudedocs/sync-conflict-2026-09-11.md.Conflict and how it was resolved
src/hooks/hook_cmd.rsd6ce8f7,6eb915b):decide/decide_for_agent/suppress_identityandHookDecisionmove to a newsrc/hooks/decision.rs;decide_from_verdictbecomes a shimget_rewrittenandenum HookDecisiongo (upstream owns them now). The fork's three already-rtk assert arms go:AllowRewrite(cmd)when every segment is already rtk and the verdict is Allow (30557a6,371e058),AskRewrite(cmd)for an already-rtk Ask or an unattestable already-rtk shape (505e355,31fceff). Upstream'ssuppress_identityis an explicit policy for the identity-rewrite case — Defer, with the Gemini trade-off documented and the contract pinned intests/hook_decision_protocol_test.rs— so the fork's arms are a competing opinion, not an addition.Consequential removals outside the hunks, all dead or unreachable once the arms are gone:
all_segments_already_rtkandpermissions::is_wrapped_invocation(only callers were the Allow arm;-D dead-codeflagged both), and therewritten == cmdearly-returns in the Copilot IDE, Vibe and Droid renderers (31fceff) thatsuppress_identitynow makes unreachable for every hook.Additive fork code kept, proved absent upstream (
git show upstream/develop:src/hooks/permissions.rs | grep -c 'normalize_for_matching\|is_rtk_prefixed\|COMMAND_WRAPPERS'→ 0): the rtk-aware permission matcher from the still-open upstream rtk-ai#3195 adoption (30557a6, albatrossflyon-coder;8adbeba,505e355amendments) and the already-rtkDenyassertion in the Claude hook. Deny is checked beforesuppress_identitycan run, sortk rm -rf /andrtk proxy rm -rf /still come back denied against aBash(rm:*)rule.Cargo.tomlin the range: version0.42.4→0.48.0and a directtoml_edit = "0.22"(already in the lock viatoml; lock diff is the version line only). Nobuild.rschange.Fork tests removed
Eighteen
hook_cmd.rstests that pinned the removed arms or renderer guards, or could no longer fail without them — full list in the writeup.test_probe_shapes_fail_safe's twocontains_already_rtk_segmentasserts moved intotest_contains_already_rtk_segment_ignores_original_form.permissions.rs:test_is_wrapped_invocation_contractdropped with its subject. Kept and still green under upstream'sdecide: the deny-through-prefix and wrapper see-through tests, the already-rtk deny payload test, and every matcher test.Fork delta
371e058and31fceffadded toHEALED_SHAS: their headline behavior is superseded by upstreamd6ce8f7.30557a6,8adbeba,505e355,d9e7458stay — the deny path, wrapper see-through andrtk runwrapper entry are live, even though two of those commit subjects still name the removed assert behavior.User-visible change
With an allow rule for the underlying tool and nothing to rewrite, an already-rtk command (
rtk grep foounderBash(grep:*)) used to get an explicitpermissionDecision: "allow"from the Claude hook; it now gets no hook output, and Claude Code's own matcher decides. Same for an already-rtk ask rule or redirect. Deny through thertkprefix is unchanged.Repro, real binaries (before = installed
developbuild of 2026-09-08, after = this branch)Sandboxed
CLAUDE_CONFIG_DIRwith{"permissions":{"deny":["Bash(rm:*)"],"allow":["Bash(grep:*)"],"ask":["Bash(git push:*)"]}}, scratchRTK_DB_PATH.Review round
Independent read-only reviewer pass over the resolution against the writeup's claims: verbatim-vs-upstream on the conflict region, additive proofs by grep on upstream's copy, guard reachability under
suppress_identity, dropped/kept test lists, stale references, HEALED_SHAS notes. All nine checks passed (conflict span byte-identical to upstream bycmp;decision.rsbyte-identical; every non-test hunk vs upstream accounted for). Four findings, all taken: the unattestable-ask arm andis_wrapped_invocationwere introduced by forkd9e7458, not505e355/31fceff— attributions corrected, andd9e7458stays in the delta because itsrtk runwrapper entry is live;test_decide_defer_for_already_rtk_command_with_no_matching_rulecould no longer fail undersuppress_identity— dropped; the 371e058 healed note pointed at the wrong commit for the survivingstrip_tokenrule — fixed; a caveat on the generated README rows for505e355/d9e7458added to the writeup. Gate re-run after the changes.Quality gate
x64 host toolchain (
scripts/win-dev-env.ps1):cargo fmt --allclean ·cargo clippy --all-targets0 warnings ·cargo test --all3540 unit tests passed, 0 failed, 8 ignored; all integration suites green, including upstream's newhook_decision_protocol_test,git_show_blob_differential_testandsignal_flush_test.git diff --checkclean.Deliberately not done
decision.rs, with its own ticket.🤖 Generated with Claude Code