From cc430c6e6f25a7dc4a7e3558b6ad529b74e9c078 Mon Sep 17 00:00:00 2001 From: "Ralph Sto. Domingo" Date: Wed, 11 Nov 2026 16:04:41 +0800 Subject: [PATCH 1/3] feat: `tool report-event` for the Claude Code plugin's lineage report - `altimate-code tool report-event --session claude:` sends one `lineage_report` usage event: the action and the session, no model, column or file names. - Only the six report actions and a `claude:` session id are accepted; the telemetry opt-out drops it. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VEmKQwEiHMwQ7BdwgPo9sb --- docs/docs/reference/telemetry.md | 1 + .../opencode/src/altimate/telemetry/index.ts | 10 ++++++++ .../src/altimate/tool-cli/report-event.ts | 21 ++++++++++++++++ packages/opencode/src/cli/cmd/tool.ts | 23 +++++++++++++++++- .../altimate/tool-cli/report-event.test.ts | 24 +++++++++++++++++++ 5 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 packages/opencode/src/altimate/tool-cli/report-event.ts create mode 100644 packages/opencode/test/altimate/tool-cli/report-event.test.ts diff --git a/docs/docs/reference/telemetry.md b/docs/docs/reference/telemetry.md index b9207d7468..f24a19521c 100644 --- a/docs/docs/reference/telemetry.md +++ b/docs/docs/reference/telemetry.md @@ -84,6 +84,7 @@ We collect the following categories of events: | `rules_delivered` | Rules added to a session's context — at the start, for a request, or for a file the agent touched — with the number of rules (and how many are team rules), their total length, the session's running total, and the token budget. No rule text or file paths. | | `rules_learning_changed` | The person turned learning from sessions on or off for a project, or back to the workspace's setting, with the resulting state and who decides it (`organization`, `you`, `workspace` or `default`). | | `rules_command` | `/rules` opened in a client (`view`), or a `learn` subcommand run (its name only, never its arguments). | +| `lineage_report` | What a person did with the Claude Code plugin's lineage report, sent by the plugin through `altimate-code tool report-event`: the end-of-task summary was shown, **Open report** was pressed, `/lineage-report` was run, or the report was built, failed to build or found no browser to open in. It carries the action and the Claude Code session id only: no model, column, file or project names. The report page itself makes no network requests. | Each event includes a timestamp, session ID, a per-launch correlation ID (`launch_id` — a random value regenerated every process start, not persisted and not derived from your machine or identity; it exists only to group events from the same run), CLI version, and a machine ID (a random UUID stored in `~/.altimate/machine-id`, generated once and not derived from your hardware or identity). When the machine is signed in to Altimate, each event also carries `user_id` and `tenant`, described under [User Identification](#new-user-identification). diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index e81c69b973..57ebaf77ee 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -918,6 +918,16 @@ export namespace Telemetry { command: string } // altimate_change end + // altimate_change start — the Claude Code plugin's lineage report, sent through `tool report-event` + | { + /** What a person did with the Claude Code plugin's lineage report. No model, column, file or project names. */ + type: "lineage_report" + timestamp: number + /** The host agent's session: `claude:`. */ + session_id: string + action: "summary_shown" | "open_pressed" | "command_run" | "built" | "build_failed" | "no_browser" + } + // altimate_change end | { // Sent before connecting, so an attempt that hangs or ends the process is still counted. type: "warehouse_connect_started" diff --git a/packages/opencode/src/altimate/tool-cli/report-event.ts b/packages/opencode/src/altimate/tool-cli/report-event.ts new file mode 100644 index 0000000000..2e31df7461 --- /dev/null +++ b/packages/opencode/src/altimate/tool-cli/report-event.ts @@ -0,0 +1,21 @@ +// altimate_change - new file +// +// `tool report-event`: the Claude Code plugin records what a person did with its lineage report, as one +// usage-telemetry event. Only the action and the host session are sent: no model, column, file or project +// names. The plugin's page itself stays offline; this runs in the CLI, under its telemetry settings, so the +// usual opt-out turns it off. + +export const REPORT_ACTIONS = ["summary_shown", "open_pressed", "command_run", "built", "build_failed", "no_browser"] as const +export type ReportAction = (typeof REPORT_ACTIONS)[number] + +/** A Claude Code session id as the plugin passes it: `claude:` and the host's own id. */ +const HOST_SESSION = /^claude:[A-Za-z0-9_-]{1,128}$/ + +export type ReportEvent = { type: "lineage_report"; timestamp: number; session_id: string; action: ReportAction } + +/** The event for an action and a host session, or why there is none. */ +export function reportEvent(action: unknown, session: unknown, now = Date.now()): ReportEvent | { error: string } { + if (!REPORT_ACTIONS.includes(action as ReportAction)) return { error: `the action must be one of ${REPORT_ACTIONS.join(", ")}` } + if (typeof session !== "string" || !HOST_SESSION.test(session)) return { error: "--session takes a Claude Code session id: claude:" } + return { type: "lineage_report", timestamp: now, session_id: session, action: action as ReportAction } +} diff --git a/packages/opencode/src/cli/cmd/tool.ts b/packages/opencode/src/cli/cmd/tool.ts index 03c35712e1..5ba4c2faf6 100644 --- a/packages/opencode/src/cli/cmd/tool.ts +++ b/packages/opencode/src/cli/cmd/tool.ts @@ -330,12 +330,33 @@ function writeAll(text: string) { } } +const ToolReportEventCommand = cmd({ + command: "report-event ", + describe: "record what a person did with the Claude Code plugin's lineage report (usage telemetry; nothing is sent when telemetry is off)", + builder: (yargs: Argv) => + yargs + .positional("action", { type: "string", describe: "what happened to the report", demandOption: true }) + .option("session", { type: "string", describe: "the Claude Code session: claude:", demandOption: true }), + handler: async (args) => { + const { reportEvent } = await import("../../altimate/tool-cli/report-event") + const event = reportEvent(args.action, args.session) + if ("error" in event) { + console.error(event.error) + process.exitCode = 1 + return + } + // Flushed, or dropped under the opt-out, when the CLI exits. + const { Telemetry } = await import("../../altimate/telemetry") + Telemetry.track(event) + }, +}) + /** How long `writeAll` waits for a reader that has stopped taking output before it gives up. */ const WRITE_STALL_MS = 60_000 export const ToolCommand = cmd({ command: "tool", describe: "run altimate-code data tools directly, without a model", - builder: (yargs: Argv) => yargs.command(ToolListCommand).command(ToolContextCommand).command(ToolSchemaCommand).command(ToolRunCommand).command(ToolDaemonCommand).demandCommand(), + builder: (yargs: Argv) => yargs.command(ToolListCommand).command(ToolContextCommand).command(ToolSchemaCommand).command(ToolRunCommand).command(ToolDaemonCommand).command(ToolReportEventCommand).demandCommand(), async handler() {}, }) diff --git a/packages/opencode/test/altimate/tool-cli/report-event.test.ts b/packages/opencode/test/altimate/tool-cli/report-event.test.ts new file mode 100644 index 0000000000..bb7e46a00a --- /dev/null +++ b/packages/opencode/test/altimate/tool-cli/report-event.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, test } from "bun:test" +import { REPORT_ACTIONS, reportEvent } from "../../../src/altimate/tool-cli/report-event" + +describe("tool report-event", () => { + test("an action and a Claude Code session make one event with nothing else in it", () => { + for (const action of REPORT_ACTIONS) { + expect(reportEvent(action, "claude:3f6c2a10-7b2e-4c1d-9a55-0e1f2a3b4c5d", 1000)).toEqual({ + type: "lineage_report", + timestamp: 1000, + session_id: "claude:3f6c2a10-7b2e-4c1d-9a55-0e1f2a3b4c5d", + action, + }) + } + }) + + test("an action outside the list is refused", () => { + for (const action of ["clicked_column", "", undefined, "built; rm -rf /"]) expect(reportEvent(action, "claude:abc")).toHaveProperty("error") + }) + + test("a session that is not claude: is refused, so no path or name can ride along", () => { + for (const session of ["abc", "codex:abc", "claude:", "claude:/home/someone/project", "claude:a b", `claude:${"x".repeat(129)}`, undefined]) + expect(reportEvent("built", session)).toHaveProperty("error") + }) +}) From 00e9116eaaec9fe30605d386cbd33efbed506466 Mon Sep 17 00:00:00 2001 From: "Ralph Sto. Domingo" Date: Thu, 12 Nov 2026 14:57:28 +0800 Subject: [PATCH 2/3] test: the `tool` help snapshot lists `report-event` Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VEmKQwEiHMwQ7BdwgPo9sb --- .../__snapshots__/help-snapshots.test.ts.snap | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/opencode/test/cli/help/__snapshots__/help-snapshots.test.ts.snap b/packages/opencode/test/cli/help/__snapshots__/help-snapshots.test.ts.snap index eeb9702758..5a59f74b6b 100644 --- a/packages/opencode/test/cli/help/__snapshots__/help-snapshots.test.ts.snap +++ b/packages/opencode/test/cli/help/__snapshots__/help-snapshots.test.ts.snap @@ -82,7 +82,6 @@ Options: [string]" `; -// altimate_change start — document the committed-answer echo allowance in run help exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode run --help 1`] = ` "altimate-code run [message..] @@ -144,7 +143,6 @@ Options: --dangerously-skip-permissions auto-approve permissions that are not explicitly denied (dangerous!) [boolean] [default: false]" `; -// altimate_change end exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode debug --help 1`] = ` "altimate-code debug @@ -531,13 +529,16 @@ exports[`opencode CLI help-text snapshots every documented command emits stable run altimate-code data tools directly, without a model Commands: - altimate-code tool list list the data tools available to \`tool run\` - altimate-code tool context print the analyst method and the connections known at startup - (what \`mcp serve\` sends as instructions) - altimate-code tool schema print one tool's description and JSON input schema - altimate-code tool run run one data tool without a model and print its result - altimate-code tool daemon [action] run, inspect or stop the long-running tool process for a - project + altimate-code tool list list the data tools available to \`tool run\` + altimate-code tool context print the analyst method and the connections known at + startup (what \`mcp serve\` sends as instructions) + altimate-code tool schema print one tool's description and JSON input schema + altimate-code tool run run one data tool without a model and print its result + altimate-code tool daemon [action] run, inspect or stop the long-running tool process for a + project + altimate-code tool report-event record what a person did with the Claude Code plugin's + lineage report (usage telemetry; nothing is sent when + telemetry is off) Options: -h, --help show help [boolean] From 354cbe81b6040a2ea1d273cd26600867ee57fc97 Mon Sep 17 00:00:00 2001 From: "Ralph Sto. Domingo" Date: Thu, 12 Nov 2026 15:02:18 +0800 Subject: [PATCH 3/3] fix: the telemetry docs call the session id the event's payload, not all it carries; the test pins the six actions Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VEmKQwEiHMwQ7BdwgPo9sb --- docs/docs/reference/telemetry.md | 2 +- packages/opencode/test/altimate/tool-cli/report-event.test.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/docs/reference/telemetry.md b/docs/docs/reference/telemetry.md index b9698b9221..e94050238b 100644 --- a/docs/docs/reference/telemetry.md +++ b/docs/docs/reference/telemetry.md @@ -84,7 +84,7 @@ We collect the following categories of events: | `rules_delivered` | Rules added to a session's context — at the start, for a request, or for a file the agent touched — with the number of rules (and how many are team rules), their total length, the session's running total, and the token budget. No rule text or file paths. | | `rules_learning_changed` | The person turned learning from sessions on or off for a project, or back to the workspace's setting, with the resulting state and who decides it (`organization`, `you`, `workspace` or `default`). | | `rules_command` | `/rules` opened in a client (`view`), or a `learn` subcommand run (its name only, never its arguments). | -| `lineage_report` | What a person did with the Claude Code plugin's lineage report, sent by the plugin through `altimate-code tool report-event`: the end-of-task summary was shown, **Open report** was pressed, `/lineage-report` was run, or the report was built, failed to build or found no browser to open in. It carries the action and the Claude Code session id only: no model, column, file or project names. The report page itself makes no network requests. | +| `lineage_report` | What a person did with the Claude Code plugin's lineage report, sent by the plugin through `altimate-code tool report-event`: the end-of-task summary was shown, **Open report** was pressed, `/lineage-report` was run, or the report was built, failed to build or found no browser to open in. Its payload is the action and the Claude Code session id, beside the fields every event carries (below): no model, column, file or project names. The report page itself makes no network requests. | Each event includes a timestamp, session ID, a per-launch correlation ID (`launch_id` — a random value regenerated every process start, not persisted and not derived from your machine or identity; it exists only to group events from the same run), CLI version, and a machine ID (a random UUID stored in `~/.altimate/machine-id`, generated once and not derived from your hardware or identity). When the machine is signed in to Altimate, each event also carries `user_id` and `tenant`, described under [User Identification](#new-user-identification). diff --git a/packages/opencode/test/altimate/tool-cli/report-event.test.ts b/packages/opencode/test/altimate/tool-cli/report-event.test.ts index bb7e46a00a..556d77cc46 100644 --- a/packages/opencode/test/altimate/tool-cli/report-event.test.ts +++ b/packages/opencode/test/altimate/tool-cli/report-event.test.ts @@ -3,6 +3,7 @@ import { REPORT_ACTIONS, reportEvent } from "../../../src/altimate/tool-cli/repo describe("tool report-event", () => { test("an action and a Claude Code session make one event with nothing else in it", () => { + expect(REPORT_ACTIONS).toEqual(["summary_shown", "open_pressed", "command_run", "built", "build_failed", "no_browser"]) for (const action of REPORT_ACTIONS) { expect(reportEvent(action, "claude:3f6c2a10-7b2e-4c1d-9a55-0e1f2a3b4c5d", 1000)).toEqual({ type: "lineage_report",