Skip to content

fix(activity): show what Claude did to the machine, as the design draws it - #22

Merged
THPubs merged 13 commits into
devfrom
fix/activity
Oct 19, 2026
Merged

THPubs merged 13 commits into
devfrom
fix/activity

Conversation

@THPubs

@THPubs THPubs commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

Why

The Activity tab is there so a person can see what Claude did on their machine: files it made or changed, what it deleted, where it went online, what it installed. It showed Claude Code's internals instead. A subagent's report headed its turn as raw <agent-message> tags, a heredoc split into one "command" per script line, searches said only "Searched the code", and tools read as "Used ScheduleWakeup".

This brings the tab in line with design A (side panel timeline).

What changed

  • What happened to the machine comes first. "Installed a package", "Deleted files", "Touched a secret file" and "Read a file outside the project" lead the title, and flagged or failed steps always keep their own row.
  • Commands read as Claude meant them. A command is titled by the description Claude already writes for it, in the past tense ("Ran the tests"), with its commands underneath. A heredoc counts as one command, and long chains show the first three. Home folders read as ~. This costs no tokens, because Claude sends the description anyway.
  • File changes show their size. Rows show +12 −3 from Claude Code's own patch. A new file reads as "Added". A command that edited files (sed -i) shows its lines too and counts under the Changes filter.
  • Test runs show their result at the row's end ("43 passed" or "2 failed"). The hook reads it from the whole output, so a long run can't lose it to the cap.
  • "you approved" marks a step the user said yes to in a permission dialog.
  • Web and search steps say where they went: "Used the internet" over the address, "Searched the web" over the query, and a code search over “pattern” · 6 files. Flag chips read "web" and "outside".
  • Reads and changes in a row fold up into "Read 9 files" or "Changed 3 files", with their lines added up, and the row opens to show each step.
  • Turns no person started are headed in plain words, for example: Agent "pr-scorer" reported back. Paste markers are taken out of prompts.
  • A main-agent step of a finished turn no longer shows "running…".
  • Subagents show their work under the step that started them, with their lines added up. A subagent whose start isn't in view gets a block of its own.
  • Bash work is visible too. A command that names a file outside the project in the home folder or the system's own (cat ~/.claude/settings.json) is flagged "outside", and a command that changed files names them ("Changed format.js, format.test.js").
  • The step detail reads as text. The hook records a command's output, a change's patch or a page's text instead of the raw JSON, keeping the start and end of a long one, and a command's input shows as the command itself.
  • More test runners and shell shapes: Node's built-in test runner results count, and a shell loop or if reads as one command.

How

  • The hook script now also records lines added and removed, whether a write made a new file, and a short outcome ("43 passed", "6 files"). It reads these from Claude Code's own tool_response, with no extra model calls.
  • activity_log::step_summary picks each step's one-line summary, and report_heading writes the heading for a turn no person started. Both live in kriyon-core and are unit-tested.
  • Activity store migration v6 adds the new columns. It fills in summaries for steps already in the log from the input they recorded. Line counts and test results start with new steps.

Testing

  • pnpm typecheck, pnpm test, pnpm test:node, cargo fmt, cargo clippy and cargo test all pass.
  • New tests cover the hook script against Claude Code's real payloads (Edit, Write, Bash with bashEditDiff, Grep, and vitest, cargo and pytest output), the store migration and backfill, the plain-word headings, and the timeline labels, chips, stats and folding.

How I tested it

  • The checks above, plus four rounds in a smoke build (pnpm smoke) against a scratch project: installs and removals, files made and changed through Bash, tests, web lookups, a subagent, and a read outside the project. Each round's screenshot drove the next fix.

Checklist

  • The PR targets dev.
  • I wrote or updated tests, and pnpm check passes.
  • I read and tested every line, and I can explain it in review.
  • AI tools wrote most of this change. (Tick if true. See Using AI tools.)

🤖 Generated with Claude Code

THPubs and others added 13 commits October 19, 2026 18:14
The Activity tab showed what Claude Code sends the model, not what a person
can read: a subagent's hand-back headed its turn as raw `<agent-message>`
tags, a heredoc split into one "command" per script line, searches said only
"Searched the code", and tools like ScheduleWakeup read as "Used
ScheduleWakeup".

Each step now keeps a one-line summary: the description Claude already writes
for a command or subagent, or the pattern, query or page a search or fetch was
about. It costs no tokens, since Claude sends it anyway. Steps already in the
log get theirs from the input they recorded.

- A command row is titled by its description, with its commands under it.
  Heredoc bodies stay part of their command, each command shows its first
  line, long chains show the first three and how many more, and home folders
  read as `~`.
- Searches, web calls, subagents and skills say what they were about, and
  Claude Code's other tools get plain titles.
- A turn started by a subagent's hand-back or a work report is headed by its
  summary ("Agent "pr-scorer" reported back"). Paste markers are taken out of
  prompts.
- A main-agent step whose end was never recorded no longer reads "running…"
  once its turn has ended.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ws it

The Activity tab exists so a person can see what Claude did: files it made
or changed, what it deleted, where it went online, what it installed. It now
follows design A (side panel timeline):

- What a step did to the machine leads its title: "Installed a package",
  "Deleted files", "Touched a secret file", "Read a file outside the
  project". Otherwise a command reads as Claude's own description in the past
  tense ("Ran the tests").
- A file change shows the lines it added and removed, from Claude Code's own
  patch, and a new file reads as "Added". A command that edited files shows
  its lines too and counts under the Changes filter.
- A test run shows its result at the row's end ("43 passed", "2 failed"),
  read from the whole output by the hook, so a long run can't lose it.
- A step the user approved in a permission dialog says "you approved".
- Web steps say where they went ("Used the internet" over the address,
  "Searched the web" over the query), and a search shows what it looked for
  and how many files it found. Flag chips read "web" and "outside".
- Reads in a row fold into "Read 9 files" and file changes into "Changed 3
  files" with their lines added up. A folded row opens to show each step.
  Flagged or failed steps never fold, so they stay in sight.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dev now takes only Claude Code's real paste format (a four-hex-digit id),
so the test's made-up id was read as prose.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Testing on a real session showed what the timeline still hid:

- A subagent's steps were mixed in with the main agent's. They now sit
  under the step that started the subagent, with their lines added up. A
  subagent whose start isn't in view gets a block of its own.
- Claude often reads and writes through Bash. A command that names a file
  in the home folder or the system's own (`cat ~/.claude/settings.json`,
  `/etc/hosts`) outside the project is now flagged "outside". A command
  that changed files names them ("Changed format.js, format.test.js") and is
  flagged like a file tool would be.
- The step detail showed a command's result as raw JSON. The hook now
  records the output itself, a change's patch or a page's text, keeping the
  start and end of a long one. Older JSON results and command inputs are
  shown as text too.
- Node's built-in test runner results ("ℹ pass 3") now show as "3 passed".
- A shell loop or `if` reads as one command, not one per piece.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A store already at v6 (from an earlier build of this branch) skipped the
column, so the timeline failed to load. It now has a v7 step of its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From the second smoke run:

- An install's node_modules and git's own files no longer count as files a
  command changed, so `npm install` doesn't list a package's LICENSE or add
  its lines to the project's.
- A subagent's hand-back heads its turn by the task Claude gave it
  ("Agent "Review format.js and add test" reported back"), not its type.
- A subagent handing back reads "Reported back", not "Used
  SubagentHandback".
- Claude loading its own tools does nothing on the machine, so it is left
  out of the timeline and its counts.
- More of Claude's verbs read in the past tense ("Appended", "Reviewed").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From the third smoke run:

- Removing a package (`npm uninstall`, `pnpm remove`, `pip uninstall`,
  `brew uninstall`, `cargo remove`, …) takes something off the machine, so
  it is flagged like a delete and titled "Removed a package".
- Every verb Claude chains in a description now reads in the past tense
  ("Installed dayjs and checked its output"), not just the first. Words
  that read as nouns after "and" ("…and test files") stay as written.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude Code forks helpers of its own: they carry an agent id but no
subagent type, since nobody launched them. One stopping a check-in loop
showed up as "Subagent · unknown" with a step that never finished, though it
did nothing on the machine. A helper's check-in is now left out like other
housekeeping, and anything else a helper does shows under "Claude Code in
the background".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A command that removed one package and installed another showed only its
most serious flag. Each flag now gets its own chip, most serious first, and
"reinstall" and "retry" read in the past tense.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t's launch

The hook took any line with "N passed" as a test result, so a run where
every test failed showed nothing and `grep passed` output got a green
badge. It now reads only the summary lines vitest, jest, pytest, cargo and
Node's runner print, and adds up every runner in one command (`pnpm check`
counts vitest, node and cargo together).

On a subagent's own event, the hook now reads the sidecar Claude Code writes
beside its transcript (`subagents/agent-<id>.meta.json`) and records which
launch started it, so a subagent can be tied to its launch exactly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…emovals

Matching a subagent to the call that launched it was written three times
(the store's SQL, the timeline and the live Agents panel), and the copies
already disagreed. kriyon-core now owns it: `exact_launch` (the launch the
sidecar names, or a background launch's agent id) before `guessed_launch`
(the earliest free launch of its type). The Activity log and the live panel
both use it, an exact link never takes a launch from another subagent, and a
launch that names no type runs as `DEFAULT_SUBAGENT`.

Removing a package is now its own step flag in core, so the frontend no
longer keeps a second, already different copy of the rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The store read a launch's subagent type from the kept input, which the hook
cuts at 2000 characters, so a subagent with a long brief lost its type. It
now keeps the type the hook sends on its own (migration v8, filled in from
the kept input for older launches), with the launch facts core links by.

Each read of a session's turns links its subagents once through core and
sends every subagent step its launch and its name; hand-back headings use the
same name. The step detail no longer sends `agentId` twice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A shell loop or `if` stays one command even when nested, keeping its
  real separators, and a here-string or `$(( … ))` is no longer read as a
  heredoc.
- Subagent steps nest under the step the store says launched them, and a
  subagent block, its launch row, its hand-back heading and the step detail
  all name it the same way.
- ", then commit" reads in the past tense; after "then" a word is always a
  verb.
- A background helper's unfinished step keeps "running…" after the turn.
- Only the real home folder is shortened to `~`, through one helper the
  Details rail shares.
- The step detail shows only a command step's command; other tools keep all
  of their input.
- Spacing comes from the theme tokens.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@THPubs

THPubs commented Oct 19, 2026

Copy link
Copy Markdown
Member Author

Review and fix — pass 1

First look at this pull request; reviewed and fixed in one sitting, then re-checked three times.

Fixed 24 issues in 3 passes. pnpm check and CI pass on the new head.

  • Subagents link to their launch exactly, by one rule in core that the Activity log and the live Agents panel share.
  • Test results come from runner summary lines only, all runners added up.
  • Package removal is a core flag; the frontend's copy of the rule is gone.
  • Shell blocks, here-strings and arithmetic split correctly and keep their separators.
  • A subagent is named one way on the timeline, in its heading and in the step detail.
  • Only the real home folder is shortened to ~.

Notes

  • One fix of mine took a guessed launch from another subagent; the final re-check caught it and it is fixed with a test.

Fixed this pass

  • src-tauri/src/activity_store.rs:935 — Store subagent_type as a column, not from capped input JSON
  • src/inspector/activityLog/activityLog.ts:750 — Stop parallel same-type subagents swapping launches
  • src-tauri/src/activity_store.rs:487 — Match a subagent to its launch once, by agentId
  • crates/kriyon-core/scripts/kriyon-hook.py:204 — Only read test counts from test-runner summary lines
  • src-tauri/src/activity_store.rs:131 — Remove the duplicate agent_id from ActivityStepDetail
  • src/inspector/activityLog/activityLog.ts:245 — Let the core say a delete was a package removal
  • src/inspector/activityLog/ActivityTab.tsx:336 — Don't give an orphan block header its child's chips
  • src/inspector/activityLog/activityLog.ts:227 — Put the verb after ', then' and 'then' in the past tense
  • src/inspector/activityLog/activityLog.ts:573 — Decide 'stale' by agentId, not agentType
  • src/inspector/activityLog/activityLog.ts:389 — Keep each block part's real separator when rejoining
  • src/inspector/activityLog/activityLog.ts:383 — Detect block keywords after do/then/else
  • crates/kriyon-core/scripts/kriyon-hook.py:199 — Combine counts from all runners in one command
  • src/inspector/activityLog/activityLog.ts:978 — Show only the command for Bash; pretty-print other inputs
  • src/inspector/activityLog/activityLog.ts:431 — Reuse one home-folder helper instead of another copy
  • src/inspector/activityLog/ActivityTab.tsx:172 — Use tokens.space for the new fold and stats spacing
  • src/inspector/activityLog/activityLog.ts:342 — Skip all of '<<<' so its tail isn't read as a heredoc
  • src/state/homeRelative.ts:10 — Shorten only the real home folder, not any /Users//
  • crates/kriyon-core/src/activity_log.rs:500 — Use one rule for matching a subagent to its launch
  • src/inspector/activityLog/activityLog.ts:779 — Name a subagent's own block the same way as its heading
  • src/inspector/activityLog/activityLog.ts:984 — Use the step's kind instead of a second list of shell tools
  • crates/kriyon-core/src/activity.rs:455 — Share one subagent-linking rule between live and stored views
  • src/inspector/activityLog/activityLog.ts:796 — Take the subagent's name from the store, not a TS copy
  • src/inspector/activityLog/activityLog.ts:801 — Reuse one format for a subagent's title
  • src/inspector/activityLog/activityLog.ts:804 — Name a step's agent the same way in the dialog as on the timeline

Checked by 5 finders (correctness, claude code rules, tests, contract, saved data, frontend rules, react lifecycle, architecture, step 5 re-check of the fixes) · architecture review · scorer: 46 scored, 24 kept.


Ready to merge, as far as this review can see. Nothing is open from this pass or any pass before it.

@THPubs
THPubs merged commit f4dc350 into dev Oct 19, 2026
13 checks passed
@THPubs THPubs mentioned this pull request Oct 20, 2026
4 tasks done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant