ccdrift 0.10.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ccdrift-0.10.0/.gitignore +15 -0
- ccdrift-0.10.0/LICENSE +21 -0
- ccdrift-0.10.0/PKG-INFO +330 -0
- ccdrift-0.10.0/README.md +301 -0
- ccdrift-0.10.0/docs/findings.md +25 -0
- ccdrift-0.10.0/lab/README.md +53 -0
- ccdrift-0.10.0/lab/__init__.py +1 -0
- ccdrift-0.10.0/lab/context.py +287 -0
- ccdrift-0.10.0/lab/early_warning.py +143 -0
- ccdrift-0.10.0/lab/failures.py +308 -0
- ccdrift-0.10.0/lab/harness.py +844 -0
- ccdrift-0.10.0/lab/latency.py +152 -0
- ccdrift-0.10.0/lab/loop_cache.py +178 -0
- ccdrift-0.10.0/lab/session_start.py +96 -0
- ccdrift-0.10.0/lab/subagent_models.py +79 -0
- ccdrift-0.10.0/lab/test_context.py +140 -0
- ccdrift-0.10.0/lab/test_early_warning.py +46 -0
- ccdrift-0.10.0/lab/test_failures.py +124 -0
- ccdrift-0.10.0/lab/test_harness.py +239 -0
- ccdrift-0.10.0/lab/test_latency.py +51 -0
- ccdrift-0.10.0/lab/test_loop_cache.py +88 -0
- ccdrift-0.10.0/lab/test_session_start.py +90 -0
- ccdrift-0.10.0/lab/test_subagent_models.py +41 -0
- ccdrift-0.10.0/pyproject.toml +55 -0
- ccdrift-0.10.0/src/ccdrift/__init__.py +3 -0
- ccdrift-0.10.0/src/ccdrift/__main__.py +3 -0
- ccdrift-0.10.0/src/ccdrift/changelog.py +117 -0
- ccdrift-0.10.0/src/ccdrift/check.py +339 -0
- ccdrift-0.10.0/src/ccdrift/cli.py +266 -0
- ccdrift-0.10.0/src/ccdrift/detector.py +170 -0
- ccdrift-0.10.0/src/ccdrift/digest.py +83 -0
- ccdrift-0.10.0/src/ccdrift/draft.py +348 -0
- ccdrift-0.10.0/src/ccdrift/early.py +143 -0
- ccdrift-0.10.0/src/ccdrift/failures.py +305 -0
- ccdrift-0.10.0/src/ccdrift/fields.py +88 -0
- ccdrift-0.10.0/src/ccdrift/history.py +426 -0
- ccdrift-0.10.0/src/ccdrift/hooks.py +98 -0
- ccdrift-0.10.0/src/ccdrift/incidents.py +320 -0
- ccdrift-0.10.0/src/ccdrift/logs.py +708 -0
- ccdrift-0.10.0/src/ccdrift/loops.py +146 -0
- ccdrift-0.10.0/src/ccdrift/notify.py +54 -0
- ccdrift-0.10.0/src/ccdrift/page.py +310 -0
- ccdrift-0.10.0/src/ccdrift/replay.py +149 -0
- ccdrift-0.10.0/src/ccdrift/report.py +303 -0
- ccdrift-0.10.0/src/ccdrift/schedule.py +461 -0
- ccdrift-0.10.0/src/ccdrift/sessions.py +349 -0
- ccdrift-0.10.0/src/ccdrift/settings.py +136 -0
- ccdrift-0.10.0/src/ccdrift/state.py +118 -0
- ccdrift-0.10.0/src/ccdrift/status.py +109 -0
- ccdrift-0.10.0/src/ccdrift/texts.py +218 -0
- ccdrift-0.10.0/tests/__init__.py +0 -0
- ccdrift-0.10.0/tests/conftest.py +8 -0
- ccdrift-0.10.0/tests/helpers.py +290 -0
- ccdrift-0.10.0/tests/test_changelog.py +137 -0
- ccdrift-0.10.0/tests/test_check.py +692 -0
- ccdrift-0.10.0/tests/test_cli.py +109 -0
- ccdrift-0.10.0/tests/test_detector.py +117 -0
- ccdrift-0.10.0/tests/test_digest.py +65 -0
- ccdrift-0.10.0/tests/test_draft.py +371 -0
- ccdrift-0.10.0/tests/test_early.py +105 -0
- ccdrift-0.10.0/tests/test_failures.py +521 -0
- ccdrift-0.10.0/tests/test_fields.py +50 -0
- ccdrift-0.10.0/tests/test_history.py +417 -0
- ccdrift-0.10.0/tests/test_hooks.py +60 -0
- ccdrift-0.10.0/tests/test_incident_commands.py +139 -0
- ccdrift-0.10.0/tests/test_incidents.py +239 -0
- ccdrift-0.10.0/tests/test_logs.py +343 -0
- ccdrift-0.10.0/tests/test_loops.py +141 -0
- ccdrift-0.10.0/tests/test_notify.py +80 -0
- ccdrift-0.10.0/tests/test_page.py +193 -0
- ccdrift-0.10.0/tests/test_replay.py +165 -0
- ccdrift-0.10.0/tests/test_report.py +319 -0
- ccdrift-0.10.0/tests/test_schedule.py +568 -0
- ccdrift-0.10.0/tests/test_sessions.py +377 -0
- ccdrift-0.10.0/tests/test_settings.py +85 -0
- ccdrift-0.10.0/tests/test_state.py +97 -0
- ccdrift-0.10.0/tests/test_status.py +206 -0
ccdrift-0.10.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Roman Kolesnichenko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
ccdrift-0.10.0/PKG-INFO
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: ccdrift
|
|
3
|
+
Version: 0.10.0
|
|
4
|
+
Summary: A check for silent changes in Claude Code's prompt caching, Haiku use, settings, context and hooks, read from your local session logs
|
|
5
|
+
Project-URL: Homepage, https://github.com/rkolesnichenko/ccdrift
|
|
6
|
+
Project-URL: Repository, https://github.com/rkolesnichenko/ccdrift
|
|
7
|
+
Project-URL: Issues, https://github.com/rkolesnichenko/ccdrift/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/rkolesnichenko/ccdrift/releases
|
|
9
|
+
Author: Roman Kolesnichenko
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: claude,claude-code,monitoring,observability,prompt-cache,regression-detection
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
17
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: System :: Monitoring
|
|
24
|
+
Classifier: Topic :: Utilities
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Requires-Dist: numpy>=1.23
|
|
27
|
+
Requires-Dist: pandas>=2.2
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# ccdrift
|
|
31
|
+
|
|
32
|
+
A check for silent changes in Claude Code, read from your own session logs.
|
|
33
|
+
|
|
34
|
+
Claude Code keeps a transcript of every session on your machine. ccdrift reads them
|
|
35
|
+
and tells you when something shifts that you'd otherwise only notice on your bill or
|
|
36
|
+
your usage limits:
|
|
37
|
+
|
|
38
|
+
- **Prompt caching gets worse.** Less of each new prompt is read from cache, so Claude
|
|
39
|
+
Code resends the conversation more often. ccdrift caught a real regression this way
|
|
40
|
+
(Claude Code 2.1.233–2.1.258, August 2026) and follows such a regression until it's
|
|
41
|
+
fixed.
|
|
42
|
+
- **Tool-loop turns start missing the cache** on the main thread and in subagents: each
|
|
43
|
+
miss writes the whole conversation to the cache again.
|
|
44
|
+
- **Haiku appears on the main thread**, where your chosen model normally answers.
|
|
45
|
+
- **A setting Claude Code picks changes:** the main thread moves between the 1-hour
|
|
46
|
+
and 5-minute prompt cache, or its effort level changes.
|
|
47
|
+
- **New sessions start with a different amount of context**, from the system prompt,
|
|
48
|
+
tools, MCP servers or CLAUDE.md.
|
|
49
|
+
- **Stop hooks start failing**, as they can when an update changes what hooks receive.
|
|
50
|
+
- **Requests start failing**, or **responses start stopping at the token limit**, far
|
|
51
|
+
more often than on the days before.
|
|
52
|
+
- **Claude Code stops logging a field** ccdrift relies on, so a silent change can't
|
|
53
|
+
hide as a quiet week.
|
|
54
|
+
|
|
55
|
+
Each alert names the Claude Code version that was running, and what a regression has
|
|
56
|
+
cost: tokens re-cached, or extra Haiku responses, and quotes the matching lines of
|
|
57
|
+
Claude Code's own release notes in the log. Claude Code keeps them in the config
|
|
58
|
+
folder that holds the transcripts: `~/.claude/cache/changelog.md`, or
|
|
59
|
+
`$CLAUDE_CONFIG_DIR/cache/changelog.md` when that variable is set.
|
|
60
|
+
|
|
61
|
+
It can't tell you whether responses think less: in one person's logs, effort swings
|
|
62
|
+
more from day to day than a 70% cut in thinking moves it. See
|
|
63
|
+
[docs/findings.md](https://github.com/rkolesnichenko/ccdrift/blob/main/docs/findings.md).
|
|
64
|
+
|
|
65
|
+
Everything stays on your machine. ccdrift reads the transcripts and keeps a state
|
|
66
|
+
file, a log, its own history of responses and, once a failing check has notified you,
|
|
67
|
+
the time it did (`check-state.json.last-failure-notice`) in `~/.ccdrift`. The history holds token
|
|
68
|
+
counts, models, versions and settings, plus each transcript's path (which includes
|
|
69
|
+
your project folder names) and session id; no prompt or response text. Only you can
|
|
70
|
+
read the history and the log, as with Claude Code's transcripts; a log an older ccdrift
|
|
71
|
+
made becomes private when you run `ccdrift schedule install` again. ccdrift sends
|
|
72
|
+
nothing anywhere, unless you give it a command to run with `--exec`.
|
|
73
|
+
|
|
74
|
+
## Install
|
|
75
|
+
|
|
76
|
+
Needs Python 3.10 or newer.
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
uv tool install ccdrift
|
|
80
|
+
# or
|
|
81
|
+
pipx install ccdrift
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
For the code that hasn't been released yet, install from the repository instead:
|
|
85
|
+
`uv tool install git+https://github.com/rkolesnichenko/ccdrift`.
|
|
86
|
+
|
|
87
|
+
Then schedule the check:
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
ccdrift schedule install # every hour, with notifications
|
|
91
|
+
ccdrift schedule install --at 18:30 --no-notify # once a day at 18:30, log only
|
|
92
|
+
ccdrift schedule status # installed? how did the last run go?
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
On macOS this adds a launchd agent. On Linux it adds a systemd user timer, or a
|
|
96
|
+
crontab line where systemd user sessions aren't available. Installing again replaces
|
|
97
|
+
the job, and keeps the old one when the new one can't be set up. On Windows, run
|
|
98
|
+
`ccdrift check` from Task Scheduler instead.
|
|
99
|
+
|
|
100
|
+
Hourly runs let ccdrift warn about cache misses within hours, once its history holds
|
|
101
|
+
200 or more new prompts in the two weeks before the latest week; the full verdict still
|
|
102
|
+
takes days. Upgrading from 0.2.0, run `ccdrift schedule install` again: the job 0.2.0
|
|
103
|
+
installed keeps running once a day until you do.
|
|
104
|
+
|
|
105
|
+
Claude Code deletes transcripts after 30 days by default. From its first run on,
|
|
106
|
+
ccdrift keeps its own history of every response it has read, so later deletions don't
|
|
107
|
+
affect it; the history grows by about 65 MB a year for a heavy user. Each check reads
|
|
108
|
+
its last 90 days, or its last 60 days of use when those reach further back, and back
|
|
109
|
+
to an older incident whose cost it works out; `ccdrift report` and `ccdrift incident
|
|
110
|
+
list` read all of it. Its first run can only see what's still on disk, and it replays
|
|
111
|
+
what it reads, its last 90 days, or its last 60 days of use when those reach further
|
|
112
|
+
back, day by day, as if it had run all along, so a regression from before you
|
|
113
|
+
installed it is recorded and reported once.
|
|
114
|
+
To give that first run more to go on, and to keep transcripts for your own digging,
|
|
115
|
+
set this in `~/.claude/settings.json`:
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{ "cleanupPeriodDays": 365 }
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## When an alert arrives
|
|
122
|
+
|
|
123
|
+
| Alert | What it means | What to do |
|
|
124
|
+
|---|---|---|
|
|
125
|
+
| **ccdrift flag** | 3 of the last 4 days passed the cutoff for the cache ratio or main-thread Haiku share. ccdrift opens an incident and names the version and the cost so far. | `ccdrift report` lists the days; `ccdrift report --by version` compares versions. A false alarm? `ccdrift incident dismiss`. A regression to report? `ccdrift incident draft` prints an issue draft. |
|
|
126
|
+
| **ccdrift: back to normal** | The metric has been back inside the cutoff, 3 days pooled, on 3 days in a row. | Nothing. `ccdrift incident list` keeps the record. |
|
|
127
|
+
| **ccdrift: change persists** | The metric hasn't recovered 30 days after the incident started. ccdrift now treats the new level as normal. | Check whether you changed something: hooks, MCP servers, model. |
|
|
128
|
+
| **ccdrift: past incidents found** | The first check replayed the history on disk day by day and found incidents ccdrift would have followed. They're recorded as if it had run all along, and one still going is flagged as well, so it arrives as what it is rather than as history. | `ccdrift incident list` shows them; `ccdrift incident dismiss` for a false alarm. |
|
|
129
|
+
| **ccdrift: cache misses rising** | Several of the latest new-prompt turns missed the cache, far above your usual rate, within the last day. | Nothing yet. The daily verdict follows within a few days; `ccdrift report` shows the days. |
|
|
130
|
+
| **ccdrift: tool-loop cache misses rising** | Several of the latest main-thread turns inside the tool loop missed the cache, far above your usual rate, within the last day. | Nothing yet. `ccdrift report` shows loop misses per day; the weekly summary shows whether it lasts. |
|
|
131
|
+
| **ccdrift: subagent cache misses rising** | The same, for turns inside subagents. | Nothing yet. `ccdrift report` shows subagent misses per day; the weekly summary shows whether it lasts. |
|
|
132
|
+
| **ccdrift: setting changed** | The cache tier or effort level a model usually gets on the main thread changed, 2 days in a row. | If you didn't change it, Claude Code's default did. |
|
|
133
|
+
| **ccdrift: session start changed** | Sessions start with at least 25% more or less context than the 10 before them, on 3 in a row. Each session is measured against its own project's recent level, so moving between projects is not a change; the alert then counts the projects ccdrift could compare, those with at least 3 sessions each side of the change, and says whether every one of them moved (Claude Code, or your global config when no new version arrived) or only some did (those projects' CLAUDE.md, MCP servers or skills; when a Claude Code version new to those sessions arrived as well, it names that too rather than choosing between them). | `ccdrift report` names each project's typical session start; `ccdrift report --by version` compares versions. |
|
|
134
|
+
| **ccdrift: hooks failing** | Stop hooks failed on at least half their runs on 2 active days in a row, after 2 quiet weeks. | Run your hooks by hand; a Claude Code update may have changed their input. |
|
|
135
|
+
| **ccdrift: requests failing** | A day had at least 5 failed requests (API errors Claude Code showed, or requests it retried), at least twice the busiest of the judged days in the 2 weeks before, with at least 5 such days to compare with. Banners blaming your Mac for going to sleep are counted in `ccdrift report` but never alert. | Usually the API or your connection, not your setup. `ccdrift report` shows the days; check status.claude.com. |
|
|
136
|
+
| **ccdrift: responses cut short** | At least 5 responses stopped at the token limit (or were refused) on a day, on at least 0.5% of that day's main-thread responses and 3 times the worst share of the judged days before, a clean fortnight counting as 0.1%. The comparison leaves out the days of the same run, so a regression that starts on a quiet day is still reported. | A Claude Code update may have changed the output limit. `ccdrift report --by version` compares versions. |
|
|
137
|
+
| **ccdrift: Claude Code stopped logging a field** | A new Claude Code version logs a field ccdrift reads on under 10% of responses. | `ccdrift peek` shows what it reads. Please open an issue. |
|
|
138
|
+
| **ccdrift can't compute the cache metric** | 3 busy days had no usable cache values. Claude Code's log format has most likely changed. | `ccdrift peek` shows the first response ccdrift finds and the fields it reads from it, with text, ids and paths shown only as their length. Please open an issue with what it prints. |
|
|
139
|
+
| **ccdrift: weekly summary** | Monday's one-line summary of the week before. | Nothing. `--no-digest` turns it off. |
|
|
140
|
+
| **ccdrift check failed** | The check itself stopped with an error. | `~/.ccdrift/check.log` has the details. |
|
|
141
|
+
|
|
142
|
+
Each alert is sent once, except responses cut short: a run that deepens to 3 times the
|
|
143
|
+
share last reported is sent again, naming the level it escalated from, and a regression
|
|
144
|
+
that simply lasts is repeated about once a fortnight. A failing check is logged on every
|
|
145
|
+
run and notifies at most once in 20 hours. Claude Code's documentation says the transcript
|
|
146
|
+
format "is internal to Claude Code and changes between versions, so scripts that parse
|
|
147
|
+
these files directly can break on any release", which is why the cache-metric alert
|
|
148
|
+
exists.
|
|
149
|
+
|
|
150
|
+
## Incidents
|
|
151
|
+
|
|
152
|
+
While an incident is open, its days stay out of the baseline, so a regression that
|
|
153
|
+
lasts for weeks is still judged against the days before it began. Against a rolling
|
|
154
|
+
baseline, the August regression looked normal again within 8 days, while 5–10% of
|
|
155
|
+
prompt turns kept missing the cache.
|
|
156
|
+
|
|
157
|
+
The first check replays the history it reads, its last 90 days, day by day and records
|
|
158
|
+
the incidents it would have followed, with the days it would have opened and closed
|
|
159
|
+
them on, then sends one alert about them, but only when it found any. `ccdrift replay`
|
|
160
|
+
runs the same replay on any install, over all of the history, not just the last 90
|
|
161
|
+
days, without recording anything or sending an alert, and says whether each incident
|
|
162
|
+
it finds is recorded.
|
|
163
|
+
|
|
164
|
+
`ccdrift incident draft` prints a Claude Code issue about an incident as Markdown, ready
|
|
165
|
+
to paste into GitHub: what changed before, during and after it, by version, the release
|
|
166
|
+
notes that may be related and your environment, as aggregates only. A cache incident's
|
|
167
|
+
draft also shows what a missed turn looks like.
|
|
168
|
+
|
|
169
|
+
```text
|
|
170
|
+
ccdrift incident list every incident, its cost and versions
|
|
171
|
+
ccdrift incident add cache 2026-08-16..2026-09-04 record one from before ccdrift ran
|
|
172
|
+
ccdrift incident close cache end the open one as of yesterday (UTC)
|
|
173
|
+
ccdrift incident dismiss cache 2026-09-14 a false alarm: its days rejoin the baseline
|
|
174
|
+
ccdrift incident draft cache 2026-08-18 an issue draft with the evidence, aggregates only
|
|
175
|
+
ccdrift replay the incidents the check would have followed
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Status line
|
|
179
|
+
|
|
180
|
+
`ccdrift status --short` prints one line when something needs attention, and nothing
|
|
181
|
+
otherwise: a failing check, no check for 3 days, an open incident, failing hooks, cache
|
|
182
|
+
misses rising, or tool-loop cache misses rising.
|
|
183
|
+
|
|
184
|
+
```console
|
|
185
|
+
$ ccdrift status --short
|
|
186
|
+
ccdrift: cache ratio down since 08-18
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
It reads only the state file and always exits 0, so it's cheap and safe to call from
|
|
190
|
+
the command your Claude Code status line runs. `ccdrift status` shows the last run,
|
|
191
|
+
open and recent incidents, setting changes, and other changes: session start, hooks,
|
|
192
|
+
fields and early warnings.
|
|
193
|
+
|
|
194
|
+
## Alerts elsewhere
|
|
195
|
+
|
|
196
|
+
`--exec` runs a command through the shell for each alert, with `CCDRIFT_ALERT` (`flag`,
|
|
197
|
+
`recovered`, `persistent`, `history`, `early`, `loop`, `subagent_loop`, `setting`,
|
|
198
|
+
`context`, `hooks`, `fields`, `blank_cache`, `digest` or `failed`), `CCDRIFT_TITLE` and
|
|
199
|
+
`CCDRIFT_MESSAGE` set. For example, to send alerts to [ntfy](https://ntfy.sh):
|
|
200
|
+
|
|
201
|
+
```sh
|
|
202
|
+
ccdrift schedule install --exec 'curl -s -d "$CCDRIFT_MESSAGE" https://ntfy.sh/your-topic'
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Anyone who knows an ntfy topic's name can read what's sent to it, so pick one that's
|
|
206
|
+
hard to guess.
|
|
207
|
+
|
|
208
|
+
A command that fails or runs longer than 30 seconds is noted in the log and doesn't
|
|
209
|
+
stop the check.
|
|
210
|
+
|
|
211
|
+
## Commands
|
|
212
|
+
|
|
213
|
+
```text
|
|
214
|
+
ccdrift check [--notify] [--exec CMD] [--no-digest] [--source DIR] [--state FILE] what the schedule runs
|
|
215
|
+
ccdrift report [--days N] [--by day|version] [--json | --html FILE] [--source DIR] [--state FILE]
|
|
216
|
+
ccdrift status [--short] [--state FILE]
|
|
217
|
+
ccdrift incident list [--source DIR] [--state FILE]
|
|
218
|
+
ccdrift incident add {cache|haiku} START..END [--state FILE]
|
|
219
|
+
ccdrift incident close {cache|haiku} [--state FILE]
|
|
220
|
+
ccdrift incident dismiss {cache|haiku} START [--state FILE]
|
|
221
|
+
ccdrift incident draft {cache|haiku} [START] [--source DIR] [--state FILE]
|
|
222
|
+
ccdrift replay [--source DIR] [--state FILE] incidents the check would have followed
|
|
223
|
+
ccdrift peek [--source DIR] the fields ccdrift reads
|
|
224
|
+
ccdrift schedule install [--at HH:MM] [--no-notify] [--exec CMD] [--no-digest] [--source DIR]
|
|
225
|
+
ccdrift schedule remove
|
|
226
|
+
ccdrift schedule status
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
`report --by version` also shows each version's median session start size once it has
|
|
230
|
+
3 or more sessions, where automatic compaction started, and up to 2 release note lines
|
|
231
|
+
about caching, Haiku and default models, effort, the system prompt and tool
|
|
232
|
+
definitions, hooks, or subagent models. `ccdrift report` also shows stop-hook runs,
|
|
233
|
+
the models subagents ran on, and tool-loop cache misses on the main thread and in
|
|
234
|
+
subagents, which `report --by version` shows as a share per version.
|
|
235
|
+
|
|
236
|
+
`report --json` holds aggregates only: no paths, session ids or project names.
|
|
237
|
+
|
|
238
|
+
`report --html FILE` writes the day view as one self-contained page: the cache ratio and
|
|
239
|
+
Haiku share drawn per day, with the days of a recorded incident on that metric shaded and
|
|
240
|
+
the flagged ones marked, a strip of each day's z under the chart with the cutoff across
|
|
241
|
+
it, a line saying what those marks mean, then the table and the sections the terminal
|
|
242
|
+
prints. It has no scripts and fetches nothing when opened, so it works offline, and it
|
|
243
|
+
names your project folders as the terminal report does. Its last line says so, since a
|
|
244
|
+
page is easier to send on than a terminal.
|
|
245
|
+
|
|
246
|
+
Transcripts are read from `$CLAUDE_CONFIG_DIR/projects` when that variable is set,
|
|
247
|
+
otherwise from `~/.claude/projects`. The state file, history and log live in
|
|
248
|
+
`$CCDRIFT_HOME`, otherwise in `~/.ccdrift`. Schedulers don't see your shell's
|
|
249
|
+
variables, so a schedule installed while either one is set keeps its value. Install
|
|
250
|
+
the schedule again after moving or reinstalling ccdrift.
|
|
251
|
+
|
|
252
|
+
## How it decides
|
|
253
|
+
|
|
254
|
+
For each complete UTC day, ccdrift looks at main-thread responses from the Claude Code
|
|
255
|
+
CLI (Agent SDK sessions are your own scripts and are left out) and computes:
|
|
256
|
+
|
|
257
|
+
- the cache read ratio on turns that open with a new prompt, within an hour of the
|
|
258
|
+
previous response and not right after a compaction;
|
|
259
|
+
- the share of responses from a Haiku model.
|
|
260
|
+
|
|
261
|
+
It counts failed requests the same way, on the main thread and in subagents alike (a
|
|
262
|
+
request a subagent made is one Claude Code made), and the responses that stop at the
|
|
263
|
+
token limit or refuse. Those are far too rare for a usual rate (13 failures in the six
|
|
264
|
+
weeks this was built on), so each day is judged against the days before it instead.
|
|
265
|
+
|
|
266
|
+
Each day is compared with the 14 days before it, leaving out the days of open and
|
|
267
|
+
recovered incidents, using their median and spread, with the spread floored at
|
|
268
|
+
sampling noise. A day is deviant past z = −3.0 for the cache ratio or z = +3.5 for
|
|
269
|
+
Haiku share, and a metric is flagged once 3 of any 4 days in a row are deviant. These
|
|
270
|
+
defaults were tuned on one person's logs; the research harness in
|
|
271
|
+
[lab/](https://github.com/rkolesnichenko/ccdrift/blob/main/lab/README.md) measures how small a change they catch on yours.
|
|
272
|
+
|
|
273
|
+
Every run also follows new-prompt turns one by one with a likelihood-ratio CUSUM that
|
|
274
|
+
tests the usual miss rate of the 14 days before the last week against 5%, the August
|
|
275
|
+
regression's rate. It warns when the sum passes h = 4 (measured in
|
|
276
|
+
[lab/early_warning.py](https://github.com/rkolesnichenko/ccdrift/blob/main/lab/early_warning.py)) within the last day, at most once a
|
|
277
|
+
week, and not while a cache incident is open. It needs 200 or more new-prompt turns in
|
|
278
|
+
those 14 days, so it stays quiet for the first three weeks or so of history.
|
|
279
|
+
|
|
280
|
+
A tool-loop turn is a response that doesn't open with a prompt, doesn't follow a
|
|
281
|
+
compaction and comes within 5 minutes of the previous response in its transcript. It
|
|
282
|
+
misses the cache when it reads less than half of what that response had cached.
|
|
283
|
+
|
|
284
|
+
Every run follows these turns one by one on the main thread and in subagents, each
|
|
285
|
+
apart, with the same kind of CUSUM against the usual miss rate of the 14 days before the
|
|
286
|
+
last week (at least 1,000 turns): on the main thread against 2% with h = 3, and in
|
|
287
|
+
subagents against 5% with h = 5 (measured in [lab/loop_cache.py](https://github.com/rkolesnichenko/ccdrift/blob/main/lab/loop_cache.py)). It
|
|
288
|
+
warns when the sum passes h within the last day, at most once a week per stream, and
|
|
289
|
+
also while a cache incident is open.
|
|
290
|
+
|
|
291
|
+
A session's start is the prompt size (input plus cache tokens) of its first response.
|
|
292
|
+
The latest 3 sessions are compared with the 10 before them: a change is at least 25%,
|
|
293
|
+
with each of the 3 more than 12.5% off on the same side.
|
|
294
|
+
|
|
295
|
+
Stop hooks count on days with 10 or more runs. They are failing when at least half
|
|
296
|
+
the runs report an error on 2 such days in a row, after at least 5 such days in the 2
|
|
297
|
+
weeks before without that.
|
|
298
|
+
|
|
299
|
+
For each Claude Code version first seen in the last 2 weeks with 50 or more
|
|
300
|
+
responses, a field logged on at least 90% of the responses in the 2 weeks before it
|
|
301
|
+
and on under 10% of the new version's is reported.
|
|
302
|
+
|
|
303
|
+
## Linux notes
|
|
304
|
+
|
|
305
|
+
- systemd user timers run only while you're logged in, unless lingering is on:
|
|
306
|
+
`loginctl enable-linger $USER`.
|
|
307
|
+
- The timer appends to the log with `StandardOutput=append:`, which needs systemd 240 or newer.
|
|
308
|
+
- A timer catches up on a run missed while the machine was off; cron doesn't.
|
|
309
|
+
- Notifications use `notify-send`. They usually appear from a systemd timer but not
|
|
310
|
+
from cron, so with cron, watch the log or use `--exec`.
|
|
311
|
+
- The systemd path is covered by tests but hasn't yet run on a real machine. Reports
|
|
312
|
+
are welcome.
|
|
313
|
+
|
|
314
|
+
## Uninstall
|
|
315
|
+
|
|
316
|
+
```sh
|
|
317
|
+
ccdrift schedule remove
|
|
318
|
+
uv tool uninstall ccdrift # or: pipx uninstall ccdrift
|
|
319
|
+
rm -rf ~/.ccdrift # state, history and log
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## Development
|
|
323
|
+
|
|
324
|
+
```sh
|
|
325
|
+
uv run --group dev --group lab pytest
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## License
|
|
329
|
+
|
|
330
|
+
MIT
|