Repository navigation
feat(claude-ops): OTEL store size cap and retention visibility - #5425
Conversation
A hot file over the cap drops its oldest lines even when every line is inside the age windows. The extra drops raise that file's structure cutoff, so they compact to cold before the trim like age drops. --dry-run reports the cap and the files it would size-prune. Refs #5236 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…re status prune-otel-store.sh stamps <store>/.last-prune after every successful non-dry run, including the nothing-to-prune short-circuit. The --otel-store probe keeps its three per-file lines and appends the cold tier size and the last-prune time and age; the observability skill tells a report to flag a missing or stale (about 2 days) prune. Refs #5236 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…p and the fileexporter limit Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ntry Refs #5236 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
PR body contract — issue linkage This PR body conforms to the issue-linkage contract. Nothing to do. |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b205438f0e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Recompute the size-cap cutoff after the Collector stops and write the last-prune stamp only after a clean restart. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 1m 39s —— View job Security review — no issues found
Scope: all 9 changed files ( What I checked:
The two Codex review comments on this PR (recompute cutoff after collector stop; write |
|
Claude finished @kyle-sexton's task in 5m 2s —— View job Code review (
|
…on tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 1m 24s —— View job Security review (
|
|
Claude finished @kyle-sexton's task in 1m 26s —— View job
|
…sizes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 50s —— View job Code review (
|
|
Claude finished @kyle-sexton's task in 30s —— View job
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
Bump claude-ops to 0.66.0, one step above main's 0.65.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
Bump claude-ops to 0.67.0, one step above main's 0.66.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
…mmand Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
Re-bump claude-ops to 0.71.0, the next free version after main's 0.70.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 3s —— View job
|
|
Claude finished @kyle-sexton's task in 2m 39s —— View job
|
Closes #5222 ## Summary Adds `/session-flow:tidy-work` (session-flow 0.41.0) and `scripts/tidy_work.py`, an opt-in lifecycle for the gitignored `.work` tiers in the current repo (the concern file's `memory_dir`, else `.work`) and `~/.work`. Nothing runs unless the skill is invoked. - `report` (read-only): per-item age, size, kind (handoff, running-retro, checklist, slice, scratch, concern, unknown) and in-flight status. A scratch item also shows the issue or PR its name carries and that item's state. - `normalize`: moves misplaced handoffs and running-retro ledgers (a handoff's `.slots.json` sidecar with it) into the standard layout; never deletes, never overwrites. - `clean`: removes an item only when it is a known kind, is not in flight, and names at least one issue or PR, every one closed or merged. A scratch item goes only once its issue is closed or its PR merged. - Unknown items (for example `.work/drain/status/*.json`) are always kept and reported. Issue body step 4 (entries that cannot be attributed are listed and never deleted) holds for every kind. An item that names no issue or PR is reported as `keep: names no issue or PR` and kept however old it is: a handoff or running retro whose text names none, and every slice and checklist, which have no attribution source (no writer records an issue or PR in them), so `clean` never removes one. The owner comment's `clean` (Expected 3) is read as acting on the attributed items that are not in flight. A handoff or running-retro file is that kind wherever it sits (the root, `handoffs/`, `running-retros/`), so `report` and `normalize` agree on it, with its `.slots.json` sidecar part of it. Attribution (issue body steps 2 to 4, owner comment's `scratch` kind): - A top-level entry is `scratch` when its name holds exactly one all-digit token of 3 to 7 digits, optionally prefixed `pr`, `issue`, or `gh` (`lint-5371.log`, `measure-4608`, `pr4120.md`, `reverify-4186`, `scratch-4586-d2cc1ea4d`). A year-like token (1900 to 2099) needs the prefix: `pr2026.md` is attributed, `backup-2026.tar` and `notes-2025.md` are not. A name with no such token, with several (a version such as `native-surfaces-2-1-284`, a date such as `triage-2026-09-28`), or starting with a writer's `<TS>Z-` timestamp is not attributed: it stays unknown and is never removed. - The number is read as an issue or PR of the repository holding the memory root, and its state is looked up: `gh api` lists the open items once per repository, then reads each other number once. A number that is no issue or PR of the repository is unknown, so it keeps the item. A PR closed without merging is `closed-unmerged` and keeps the item. - `report` and each `clean` dry-run path show every state looked up, for example `stale [#4608 closed]` or `would remove: <path> [#5371 merged]`, so the confirmation covers the issue or PR each path was matched to (handoffs and retros included). - The issue lists the name, a marker file, or the producing branch as attribution sources. Only the name is read for scratch: the repo defines no marker-file convention, and no `.work` writer records a branch (handoff frontmatter is `type`/`handoff_shape`/`date`/`topic`/`session_id`/`transcript`/`previous_handoff`/`chain`). Step 3 offers delete or consolidate; `clean` deletes. - In `~/.work` a bare number has no repository, so a scratch item there is unknown-state and always kept. In flight, so kept: - a slice whose `INDEX.md` `status:` is not `done`, or that holds a child slice whose status is not `done` (the topic-docs contract makes that frontmatter field the slice's state; missing and unrecognized values keep it too) - a workflow checklist with an unfinished stage - anything with a `.git` file or directory under it: a clone or worktree can hold commits that exist nowhere else, and the tracked-path guard only asks the outer repository - a change inside `--days` (default 14) - a later handoff that is itself kept and names the item. A stale handoff does not keep what it names. - a handoff or running retro whose text names an issue or PR that is not closed or merged (a `github.com` URL, `owner/repo#N`, or `#N`), or one whose state could not be read - a scratch item whose number is open, a PR closed unmerged, or unreadable No writer records an issue or PR in frontmatter, so handoff and retro references are read from the text. A bare `#N` means the repository holding the memory root; in `~/.work` it counts as unknown. A link is looked up only for an item nothing cheaper already keeps, except a scratch item's, whose state is the attribution the report shows. The `.git` check likewise runs only on an item nothing cheaper keeps. The contract designates no top-level `.work` directory as scratch by name, so the entries of `reviews/`, `exports/`, `overengineering/`, `enforceability/`, `docs-hygiene/`, and `lanes/` are kind `concern`: reported, never removed, because their owning skills read them back. Other tools' free-form output without an issue or PR number (for example `~/.work/local-otel-claude-code`) is unknown and kept. ## Fix Placement is session-flow, not repo-hygiene as the issue title says: the issue body proposes `/session-flow:tidy-work`, session-flow owns the `.work` layout and the `save_point.py` frontmatter parsers, repo-hygiene cannot import them without a cross-plugin dependency, and `~/.work` is not a repo. The "call disk-hygiene's protection list" line is a proposed direction, not an expected item, and disk-hygiene has no public entry point for it (only its skill-private `baseline-policy.json`), so reading it would break skill encapsulation. It is not read. `normalize` and `clean` are dry runs that print exact absolute paths until `--apply`. They never modify content git tracks, following the topic-docs runtime guards: - a memory root whose `.gitignore` lacks a line `*` is refused (`~/.work` excepted, which has no fixed self-ignore file) - every command, `report` included, rejects an existing memory root outside the repository whose `.gitignore` lacks a line `*` (exit 2, nothing listed). `memory_dir` comes from the repo-controlled `.claude/topic-docs.yaml` and `report` is pre-approved, so `memory_dir: /etc` or `../outside` is refused before anything is enumerated. This is the guard the handoff writer requires of every memory root; a root below the repository is unaffected, and a root that does not exist yet reports as empty - an item with a tracked path under it is refused - every command rejects a memory root that is the repository root (`--memory-dir .`) - a relative `--memory-dir` resolves against the repository top level - `--apply` also refuses paths outside the resolved roots and symlinks leaving them The skill resolves `memory_dir` through `parse-concern-value.sh` as handoff does and passes `--memory-dir`. It pre-approves only the read-only `report` invocation and the read-only parse helper; `--apply` stays behind the permission flow. Version bump 0.40.2 to 0.41.0 (minor), changelog entry above main's 0.40.x entries, README, `topic-docs.md`, catalog and cheat sheet updated. ## Verification - `uvx pytest -q plugins/session-flow/scripts/tests/test_tidy_work.py`: 45 passed (186 with `test_save_point.py`). Nine cases fail against the previous `tidy_work.py`: the unattributed handoff, running retro, `done` slice and finished checklist are kept and absent from the dry run and `--apply` (tree listing unchanged); a kept handoff that names no issue keeps the scratch directory it names; the year names (`_name_refs` directly, and `backup-2026.tar` and `notes-2025.md` beside a closed `#2026` and a merged `#2025` in the link table) stay unknown while `pr2026.md` is attributed and removed once `#2026` is closed. The symlink-escape, tracked-content and missing-self-ignore cases now remove an attributed scratch directory or handoff (`measure-4608` with `#4608` merged, a handoff naming a closed `#7`) instead of an unattributed one, and each fails when its guard is disabled. The stale-handoff-chain case runs on attributed handoffs and a scratch directory they name. The memory-root cases still cover an outside root (absolute and `../outside`) rejected by all three commands with nothing listed and reported once its `.gitignore` holds `*`, an absent outside root, every file `normalize` would move classified as a known kind, and `clean --apply` on a stale root handoff with its sidecar. The scratch cases use real `.work` names: six attribute (`lint-5371.log`, `measure-4608`, `pr4120.md`, `reverify-4186`, `scratch-4586-d2cc1ea4d`, `pr2026.md`) and eleven do not; dry-run and apply remove only closed or merged scratch; a number that is no issue or PR keeps its item; an item holding a `.git` directory or file survives even with a merged number. - `scripts/run-ruff.sh check plugins/session-flow/scripts`: all checks passed; `format --check` clean on `tidy_work.py` and its test. - Read-only `tidy_work.py report --days 0 --memory-dir <this repo's .work>` with live `gh` (`--days 0` turns off the recency window; at the default 14 days every entry there is kept): 87 items, 6 removable, 81 kept. Removable: `lint-5371.log` `[#5371 merged]`, `measure-4608` `[#4608 closed]`, `pr4120.md` `[#4120 closed]`, `pr5425-body.md` `[#5425 merged]`, `scratch-4586-d2cc1ea4d` `[#4586 closed]`, and one handoff whose text names only merged and closed items, with its sidecar. `reverify-4186` is kept because it holds five git clones and #4186 is open; `native-surfaces-2-1-284` and `triage-2026-09-28` are unknown and kept; each of the 14 handoffs names an issue or PR, so none is kept for lacking one. `clean --days 0` without `--apply` listed those paths with their `[#N state]`; `--apply` was not run. - `scripts/check-changelog-parity.sh --check --check-order`: pass; `scripts/validate-plugins.sh`: pass; `generate-catalog.mjs --check` and `generate-cheatsheet.mjs --check`: in sync - `CHECK_SKILL_SKILLS_ROOT=plugins/session-flow/skills bash plugins/skill-quality/scripts/check-skill.sh tidy-work`: PASS, 0 warnings; `check-evals-quality.sh` on the skill's `evals.json`: 0 warnings; `check-purged-em-dashes.sh`: no em dashes; `markdownlint-cli2` on the touched markdown: 0 issues - `bash scripts/affected-tests.sh --run --jobs 4` (shell suites, rerun on this head): all pass except `scripts/check-script-contract.test.sh`, whose two `check-html-assets.sh` cases need `npm ci` (htmlhint absent), unrelated to this change. The selection also names two Python suites the shell runner does not execute, `test_tidy_work.py` (run above with `uvx pytest`) and `test_audit_skill_visibility.py` (untouched by this branch) - `git ls-files -s plugins/session-flow/scripts/tidy_work.py`: mode 100755, as the lint exec-bit step requires of a shebang file (`save_point.py` is 100755) - `bash plugins/session-flow/scripts/tidy_work.test.sh`: SKIP locally (system python3 lacks pytest); the pytest run above covers it ## Related Related: #3555, #4295, #4228. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Refs: #5236
Summary
The OTEL store had no size bound and its status output hid cold size and prune age. This adds a hot-file size cap, reports cold size and last-prune age, and corrects the retention docs.
Fix
prune-otel-store.sh:CC_OTEL_HOT_MAX_MBcaps each hot store file as a fallback after age pruning (the file exporter appends and cannot rotate).probe-observability-state.sh --otel-store: prints cold size and time since the last prune.operator-setup-retention.mdand the observability SKILL: measured sizes, the cap, and the file exporter limit.Verification
prune-otel-store.test.sh: 176 passed, 0 failedprobe-observability-state.test.sh: 64 cases passedscripts/check-changelog-parity.sh --check --check-order: passscripts/validate-plugins.sh: all manifests and catalog validatedRelated
Refs #5236. The ADR-0009 acceptance criterion lives in melodic-software/medley and stays with the owner, so this PR does not close the issue. #5274 (stable prune entry point) is related and not addressed here.
🤖 Generated with Claude Code