From af6f27cf6093a7e3f0fe796e9f0e065c741c15d5 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 8 Aug 2026 04:28:26 +0000 Subject: [PATCH 1/2] Filter bash output through rtk before it reaches the model Register a PreToolUse hook on Bash that rewrites each command through rtk, so `git status` runs as `rtk git status` and the output is compressed on the way back. The rewrite is transparent; commands are still written normally. Measured on this repo, a full `git diff HEAD~3` drops from 521 KB to 28 KB, a recursive grep over app/src/main from 117 KB to 19 KB, and `git log -20` from 20 KB to 5.3 KB. Small outputs grow marginally because rtk adds a header, which at those sizes is not worth avoiding. The container starts empty, so a SessionStart hook installs the binary first. It pins RTK_VERSION because the installer's default is to resolve the latest tag through api.github.com, which the proxy answers 403 for every repository but this one. Outside the sandbox the hook installs nothing and only prints the command to run, and every failure path exits clean: with rtk absent the rewrite no-ops and the only cost is verbose output. Verified that rtk leaves what must not change alone -- `rtk read` returns GameSetupScreen.kt byte-for-byte, commit messages keep their quoting, and already-rewritten commands are not wrapped twice. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Urn6eEBA6XzrnfoibL7faC --- .claude/hooks/session-start.sh | 39 +++++++++++++++++++++++++++++ .claude/settings.json | 25 +++++++++++++++++++ .rtk/filters.toml | 13 ++++++++++ CLAUDE.md | 45 ++++++++++++++++++++++++++++++++++ 4 files changed, 122 insertions(+) create mode 100755 .claude/hooks/session-start.sh create mode 100644 .claude/settings.json create mode 100644 .rtk/filters.toml diff --git a/.claude/hooks/session-start.sh b/.claude/hooks/session-start.sh new file mode 100755 index 0000000..fa797ef --- /dev/null +++ b/.claude/hooks/session-start.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# Make sure rtk is on PATH, so the PreToolUse hook in .claude/settings.json has +# something to call. Without this the sandbox starts every session with an empty +# container and the rewrite hook quietly no-ops. +# +# Deliberately never fails: a missing rtk costs tokens, not correctness, and a +# session that refuses to start because a compression tool did not download +# would be a much worse trade. + +set -uo pipefail + +RTK_VERSION="v0.45.0" +INSTALLER="https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/install.sh" + +export PATH="$HOME/.local/bin:$PATH" + +if command -v rtk >/dev/null 2>&1; then + exit 0 +fi + +# Only install unattended in the ephemeral web sandbox. On a personal machine, +# say what to run instead — dropping a binary into someone's ~/.local/bin +# without asking is not ours to do. +if [ "${CLAUDE_CODE_REMOTE:-}" != "true" ]; then + echo "rtk is not installed, so bash output will not be compressed." + echo "To install it: curl -fsSL $INSTALLER | sh" + exit 0 +fi + +# The version has to be pinned. The installer's default is to look up the latest +# tag through api.github.com, which the sandbox proxy answers with a 403 for +# every repository except this one, so the lookup fails and takes the install +# with it. Bump this by hand. +if ! curl -fsSL "$INSTALLER" 2>/dev/null | RTK_VERSION="$RTK_VERSION" sh >/dev/null 2>&1; then + echo "rtk $RTK_VERSION did not install; continuing without output compression." + exit 0 +fi + +echo "rtk $(rtk --version 2>/dev/null | awk '{print $2}') ready; bash output is compressed." diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..afcdbb8 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,25 @@ +{ + "hooks": { + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/session-start.sh" + } + ] + } + ], + "PreToolUse": [ + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": "command -v rtk >/dev/null 2>&1 && exec rtk hook claude || exit 0" + } + ] + } + ] + } +} diff --git a/.rtk/filters.toml b/.rtk/filters.toml new file mode 100644 index 0000000..d9bd43f --- /dev/null +++ b/.rtk/filters.toml @@ -0,0 +1,13 @@ +# Project-local RTK filters — commit this file with your repo. +# Filters here override user-global and built-in filters. +# Docs: https://github.com/rtk-ai/rtk#custom-filters +schema_version = 1 + +# Example: suppress build noise from a custom tool +# [filters.my-tool] +# description = "Compact my-tool output" +# match_command = "^my-tool\\s+build" +# strip_ansi = true +# strip_lines_matching = ["^\\s*$", "^Downloading", "^Installing"] +# max_lines = 30 +# on_empty = "my-tool: ok" diff --git a/CLAUDE.md b/CLAUDE.md index 98dcdf1..858e8b5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -67,3 +67,48 @@ The suppression is a property of the sandbox's credential, so it cannot be fixed from inside a session. Giving the environment a personal access token to push with would make pushes and PRs behave normally for everyone. Until then, the dispatch step above is the workaround. + +## Bash output is filtered before it reaches the model + +`.claude/settings.json` registers a `PreToolUse` hook on `Bash` that rewrites +each command through [rtk](https://github.com/rtk-ai/rtk): `git status` runs as +`rtk git status`, and rtk compresses the output. Write commands normally — the +rewrite is transparent and costs nothing to invoke. + +Measured on this repo rather than quoted from rtk's README: + +| Command | Raw | Filtered | Saved | +| --- | --- | --- | --- | +| `git diff HEAD~3` | 521 KB | 28 KB | 95% | +| `grep -rn "fun " app/src/main` | 117 KB | 19 KB | 84% | +| `find . -name "*.kt"` | 7.7 KB | 1.7 KB | 79% | +| `git log -20` | 20 KB | 5.3 KB | 74% | +| `git diff --stat` | 4.0 KB | 4.0 KB | 1% | +| `ls` of a single package | 50 B | 62 B | −24% | + +The saving is all in the large outputs, which is the point — a full diff of a +few commits is otherwise most of a context window. Already-compact output grows +slightly, because rtk adds a header; at those sizes it does not matter. `rtk +gain` reports what the current session has actually saved. + +Two limits are worth knowing. The `Read`, `Grep` and `Glob` tools are not +`Bash`, so they never reach the hook and are unaffected; a deliberate `rg` or +`find` in a shell *is* filtered. And filtering never truncates a file: `cat` +becomes `rtk read`, which was checked byte-for-byte against this repo's longest +Kotlin source (`GameSetupScreen.kt`, 946 lines) and came back identical. Commit +messages survive the rewrite intact, quotes and colons included. To bypass +filtering for one command, run `rtk proxy `. + +## rtk is reinstalled at the start of every session + +The container starts empty, so `.claude/hooks/session-start.sh` installs rtk +before the session does anything. It pins `RTK_VERSION`, because the installer +otherwise asks `api.github.com` for the latest tag and the proxy answers 403 for +every repository except this one — the lookup fails and takes the install with +it. Bump that variable by hand. + +The hook is written not to matter when it fails. Outside the sandbox it installs +nothing and just prints the command to run, since dropping a binary into +someone's `~/.local/bin` uninvited is not its business; if the download fails it +says so and exits clean. With rtk absent the rewrite hook no-ops, and the only +consequence is verbose output. From ee94d073dd64d64528677c3a3d1ff538108d4691 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 8 Aug 2026 04:35:56 +0000 Subject: [PATCH 2/2] Open the 1.2 series The number after the series is the CI run number and climbs on its own, so this is the only part of a version that is set by hand. Dev builds published from dev now tag v1.2.-dev. The example in the comment above tracked the old value; a stale copy of the number is the exact thing that comment warns about, so it moves too. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Urn6eEBA6XzrnfoibL7faC --- gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index b8b27b0..cf29b77 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,10 +16,10 @@ android.nonTransitiveRClass=true # ., a production tag is v. and a dev tag adds # -dev. It lives here rather than in the build file because the workflows have # to name the tag as well, and they read it straight out of this file — three -# copies of "1.1" would be three chances for a release to be tagged one thing +# copies of "1.2" would be three chances for a release to be tagged one thing # and built as another. # # Only the series is set by hand. The number after it is the CI run number, so # it keeps climbing on its own and an APK's versionCode never goes backwards # when this changes. -plgVersionSeries=1.1 +plgVersionSeries=1.2