Follow-up to the initial #1536 fix after a deeper review pass.
- force_tee_hint() is no longer called on the passthrough path. Previously
every non-TTY pipe invocation (curl | jq, curl | python -m json.tool —
the exact scenarios this PR fixes) was still writing a tee log file as a
side effect, which is wasteful: the tee file's purpose is to give the
LLM a recovery path when output is truncated, and we are no longer
truncating in those cases.
- looks_like_json now also matches bare top-level JSON strings (e.g.
/api/token endpoints returning a long quoted token). Previously a
>500-byte bare JSON string on a TTY would still get truncated mid-stream
and produce invalid output.
- FilterResult.content is now Cow<'_, str> so the passthrough path no
longer copies the whole body. For multi-MB curl responses piped through
rtk this eliminates a full-size allocation.
- Exit code from curl is propagated via Ok(result.exit_code) instead of
the previously hardcoded Ok(0). On the success path the value is 0
anyway, but the inconsistency with the early-exit branch above made the
intent unclear.
Tests: 9 -> 11 (added bare-JSON-string passthrough and a Cow::Borrowed
assertion verifying the passthrough paths don't allocate). cargo test
--all: 1693 passed, 6 ignored.
Verified end-to-end against jsonplaceholder.typicode.com/posts (~27 KB,
100 entries, parseable) and a randomly-generated 100-entry / 52 KB / 1450-line
JSON served from a local http.server (rtk output byte-identical to source
modulo the trailing newline added by println!).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`rtk curl` was unconditionally truncating any response >=500 bytes when a
tee hint was available, inserting the literal `... (N bytes total)` marker
mid-stream. For JSON bodies this produces invalid, unparseable output and
silently breaks every downstream `jq`, `python -m json.tool`, or agent
pipeline that consumes `rtk curl`.
Two changes in `filter_curl_output`:
1. JSON heuristic passthrough — if the trimmed body starts/ends with
matching JSON brackets, return it unchanged. The body is already
buffered in memory so the check is essentially free.
2. `is_terminal()` gate — only truncate non-JSON output when stdout is
a TTY. Pipes and shell redirects (`> file`, `| jq`) now receive the
full body, matching what `curl -o file` already does and closing
the silent-data-loss path described in #1282.
The tee file is still written for inspection, but the hint line is
suppressed in the passthrough cases so it never leaks into pipes.
Tests: existing 5 cases updated to pass `is_tty=true` (preserve TTY-mode
assertions); 4 new cases cover JSON object/array passthrough at
>500 bytes and pipe-mode (non-TTY) full-body delivery.
Verified end-to-end against the public repro from the issue:
`rtk curl -s https://jsonplaceholder.typicode.com/posts | python3 -c
'import sys,json; print(len(json.load(sys.stdin)))'` now returns 100.
Closes#1536
Refs #1282
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
KuSh: the in-progress state line ("rebase in progress", "merge in
progress. unresolved conflicts", etc.) is enough — LLMs know which git
commands resolve each state, so the per-state hint list was noise.
Removes STATE_HINTS and extract_state_hint, and reduces extract_state_header
to a short walk that returns the first detected state summary or None.
Existing per-state tests now assert the exact compact summary.
- Drop redundant LANG=C env (LC_ALL=C is sufficient).
- Replace nested if/else chains in detect_status_state and
extract_state_hint with const slices + iter for readability.
- Drop the 2-space indent prefix on hint lines — adds nothing for
machine consumption.
Default `rtk git status` runs `git status --porcelain -b` to build its
compact view, but porcelain v1 omits the state header git prints when a
rebase, merge, cherry-pick, revert, bisect, am, or sparse checkout is in
progress. That header is correctness-critical — hiding "You are currently
editing a commit while rebasing..." leaves the user thinking the repo is
clean when it isn't.
`run_status` already captures plain `git status` output as `raw_output`
for tracking, so this fix adds `extract_state_header` to pull the state
block out of it and prepends the block to the compact output. Returns
`None` when nothing is in progress so clean repos are unchanged.
Covered states: interactive / regular rebase, merge-with-unmerged-paths,
"still merging after conflicts fixed", cherry-pick, revert, bisect, am,
sparse checkout. Preserves the directive hints git prints alongside
(`git rebase --continue`, `git commit --amend`, `git bisect reset`,
etc.), while still filtering generic `(use "git add")` / `(use "git
restore")` noise.
Design principle: Correctness vs Token Savings — never hide information
that changes the user's understanding of repo state.
Per claude-code#24067, underscore is replaced with '-'.
Per claude-code#40946, non-ASCII characters are each replaced with '-'.
Windows backslashes follow the same rule.
Refactored encode_project_path to use chars().map() instead of
chained replace() calls for clarity.
Closes#1457
Claude Code replaces both '/' and '.' with '-' when deriving a
projects-directory name from the working-directory path. rtk only
replaced '/'. For users whose path contains a dot (e.g. /Users/first.last)
the computed slug didn't match Claude's directory name, so rtk discover
found zero sessions.
Fixes#1457
Restores src/core/runner.rs to the develop state. After a5e6bf5 moved
npx execution into npm_cmd::exec (routing through runner::run_filtered),
nothing calls run_passthrough_cmd anymore.
Addresses review feedback on PR #1458.
Add `npm_cmd::exec` for npx and an internal `run_filtered` helper that
both `run` (npm) and `exec` (npx) share. The helper resolves the
command, applies args, honors SKIP_ENV_VALIDATION, and routes through
runner::run_filtered with the npm output filter. The npx fallback in
main.rs collapses to `npm_cmd::exec(&args, cli.verbose, cli.skip_env)?`.
Per @KuSh review on #1458.
Per @KuSh's review on #1458, the generic npx passthrough arm built
its own Command + tracking::TimedExecution inline, duplicating the
logic already covered by core::runner::run_passthrough. The sticking
point was `SKIP_ENV_VALIDATION`: the existing helper takes tool +
args and builds the Command itself, so there was no seam for a
caller-supplied env var.
Add a `run_passthrough_cmd` variant that accepts a pre-built Command
and refactor the existing `run_passthrough` to delegate to it. The
npx arm now builds a Command, sets SKIP_ENV_VALIDATION when
requested, and hands it to the shared helper. This also upgrades the
npx passthrough from a raw `cmd.status()` to the shared streaming +
tracking path that pnpm_cmd already uses, so verbose logging and
telemetry stay consistent across tools.
The generic fallback arm of `Commands::Npx` called `npm_cmd::run`, which
executes `npm` instead of `npx`. This broke every non-routed npx
invocation. `rtk npx cowsay hello` failed with `npm error Missing
script: "cowsay"` because the rewrite hook sends `npx ...` through as
`rtk npx ...`, and `rtk npx` was then running npm.
Mirror the existing prisma generic-subcommand passthrough pattern:
resolve npx, forward all args, record a tracked passthrough, and
propagate the exit status.
Fixes#815
- Force LC_ALL=C so ls always outputs English month names
- When zero lines parsed but directory has content, fallback to raw output
- Add is_dotdir() to distinguish empty dirs (only . and ..) from unparseable content
- Fix empty directory regression for both English and non-English locales
- Closes#1276