- Decompose compound commands for permission checks (newline, background &, subshell `( )`) in addition to &&, ||, ;, | so hidden segments are checked.
- `contains_unattestable_construct`: flag command/process substitution and file-target redirects (fd-dup `2>&1` and /dev/null exempt) — RTK can't decompose these, so they are never auto-allowed.
- Route every host hook (Claude, VS Code, Gemini, Cursor, Copilot CLI) through a single decision flow. Precedence: Deny → (defer if unattestable) → Allow → Ask → Default. Auto-allow only on a positive Allow; otherwise defer to the host's own engine.
- Gemini: ask_user instead of hardcoded allow. Cursor: empty `{}` delegation, since permission:"ask" is not enforced on its sandboxed shell.
When `filter_markdown_body()` strips a PR or issue body to empty
(body contained only badges, images, HTML comments, or horizontal
rules), `format_pr_view` and `format_issue_view` previously skipped
the body section silently. Users had no indication that body content
had been present and filtered.
Now both views emit a fallback note when the filtered body is empty
but the raw body was not:
PR view: (body contained only badges/images/comments)
Issue view: Description: (body contained only badges/images/comments)
The 4 added tests cover:
- PR body with only badges/images/comments -> fallback note appears
- PR body with real content -> no fallback note (sanity)
- PR body empty (raw) -> no fallback note (no signal to give)
- Issue body badges-only -> fallback note appears
Closes#235
Co-authored-by: polaminggkub-debug <polaminggkub-debug@users.noreply.github.com>
atomic_write() replaced symlinked settings.json with a regular file.
Now resolves symlink targets before the tempfile+rename so the link
itself is preserved and the real file gets updated.
Covers both absolute and relative symlink targets.
Closes#653
`rtk curl <binary-url>` silently corrupted any binary download
(tarballs, zip, png, pdf, ELF, ...) because the response body was
captured via `String::from_utf8_lossy` in `core::stream::exec_capture`.
Every non-UTF-8 byte got replaced with U+FFFD (3 bytes: 0xEF 0xBF 0xBD),
so gzip magic 1f 8b 08 00 arrived at downstream consumers as
1f ef bf bd 08 00 and `tar -xzf` complained "not in gzip format".
Now `rtk curl` runs `Command::output()` directly to keep stdout as
`Vec<u8>`, then checks `std::str::from_utf8(&bytes).is_err()`. If the
response is not valid UTF-8 (i.e. the lossy conversion would corrupt
it), raw bytes are written through to stdout via `write_all` and
tracking is recorded as passthrough (0% savings — token counts over
binary content have no meaning). The text/JSON code path is unchanged.
Verified live on 18 real binary formats (rtk's own release artifacts,
ripgrep, bat, fd, hyperfine, gh, tokei, kubectl ELF 51MB, rustup-init
20MB, W3C PDF, ISO 9899 PDF 4MB, GitHub avatar PNG, Wikimedia GIF,
WebP sample, MP3/MP4/WAV samples) — all byte-identical to raw curl.
10 text regression tests (JSON/HTML/Markdown/Cargo.toml/RFC) confirm
the text path keeps its existing behavior.
Closes#1087
The generic AWS handler (used for subcommands outside the hardcoded
specialized list) called json_cmd::filter_json_string, which extracts
a type-only schema and discards every value.
$ rtk aws backup describe-global-settings --output json
{
GlobalSettings:
{
isCrossAccountBackupEnabled: string,
isDelegatedAdministratorEnabled: string,
isMpaEnabled: string
}
LastUpdateTime: string
}
Callers got the schema instead of the data they asked for.
Swap to json_cmd::filter_json_compact, which preserves values while
still applying depth, string-length, and array-size truncation:
$ rtk aws backup describe-global-settings --output json
{
GlobalSettings:
{
isCrossAccountBackupEnabled: "false",
isDelegatedAdministratorEnabled: "false",
isMpaEnabled: "false"
}
LastUpdateTime: "2026-05-28T09:52:17.525000+02:00"
}
Affects every AWS subcommand not on the hardcoded list (backup,
route53, kms, ssm, apigateway, ...) when output is valid JSON
(explicit `--output json` or auto-injected for describe-/list-/get-/scan).
gh and glab both accept several subcommands without an explicit id and
resolve it from the current branch (gh pr view, gh pr checks, glab mr
view) or open an interactive picker (gh run view). RTK was pre-rejecting
with "X number required" before the underlying tool ever ran.
Six call sites now treat the identifier as optional and forward the
request unchanged, letting gh/glab decide:
- gh pr view, gh pr checks, gh issue view, gh run view
- glab mr view, glab issue view
A new `parse_optional_identifier` helper in each file captures the
"(Option<id>, Vec<extra>)" parse so the no-id branch is unit-testable.
format_run_view also drops the dangling `#` from its header when the
run id is empty (interactive-picker passthrough).