feat(cli): surface the trace session link to the user - #3430
Conversation
After `chainloop trace` attests an AI coding session, log one line per attested session pointing at its dashboard page. The link is only shown when the control plane reports a ui_dashboard_url, reusing the value the crafting state already carries from attestation init rather than making an extra request from inside a git hook. Without a dashboard the line still names the session. The URL is built from the agent's own session ID, the only identifier available at push time, which the dashboard resolves alongside the Chainloop UUID. Closes #3429 Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 2cee6265-17c4-487a-b31d-072037bd3bc7
AI Session Checks — 🟡 80% ·
|
| Avg score | Sessions | Failing policies | Attribution | Files | Lines | Total Duration |
|---|---|---|---|---|---|---|
| 🟡 80% | 3 | 85% AI / 15% Human | 25 | +1276 / -121 | 3h27m6s |
🟡 75% — 42% AI — ✅ All policies passing
-
Sep 10, 2026 20:39 UTC · 8m17s · $2.86 · 88 in / 24.1k out · claude-code 2.1.268 (claude-opus-5)
Change Summary
-
- Inspects the dirty tree around
.opencode,.cursor, andtrace_banner_test.gobefore acting. - Replaces repeated dashboard URL literals in
trace_banner_test.gowith the sharedtestDashboardURLconstant. - Leaves
.opencode/plugins/chainloop-trace.tsmodified and.cursor/hooks.jsonuntracked; no commit or push happened.
- Inspects the dirty tree around
AI Session Overall Score
-
🟡 75% — Unfinished commit flow and incomplete final verification keep this out of green.
AI Session Analysis Breakdown
-
🟢 88% · solution-quality
-
🟢 AI used the existing shared test constant instead of a quick lint-silencing workaround. · High Impact
🟢 86% · scope-discipline
-
No notes.
🟢 84% · context-and-planning
-
🟢 AI clarified whether
.cursor/hooks.jsonshould be committed before acting on it. · High Impact
🟢 82% · user-trust-signal
-
No notes.
🟡 63% · verification
-
🟢 AI ran targeted and broader
go testcommands and captured both pass results. · Medium Impact🟠 Earlier tests passed, but the last edit never got a fresh test/lint rerun after the interrupted command. · Medium Severity
💡 After the final edit, rerun the exact verification command before you summarize the state.
🟡 58% · alignment
-
🟠 The requested commit/push never happened;
.opencodeand.cursorwere still pending at the end. · Medium Severity💡 When the ask is commit/push, either complete it or state early that you are pausing the flow.
-
File Attribution
████████░░░░░░░░░░░░42% AI / 58% HumanStatus Attribution File Lines modified ai app/cli/pkg/action/trace_banner_test.go+88 / -10 created human app/cli/internal/trace/providers/capabilities_test.go+74 / -0 modified human app/cli/pkg/action/trace_agent_hook.go+25 / -9 modified human app/cli/internal/trace/claude/provider.go+6 / -0 modified human app/cli/internal/trace/cursor/provider.go+6 / -0 modified human app/cli/internal/trace/opencode/provider.go+6 / -0 modified human app/cli/internal/trace/provider.go+6 / -0
Policies (4)
Status Policy Material Messages ✅ Passed ai-config-ai-agents-allowedai-coding-session-820308- ✅ Passed ai-config-no-dangerous-commandsai-coding-session-820308- ✅ Passed ai-config-no-secretsai-coding-session-820308- ✅ Passed ai-config-mcp-servers-allowedai-coding-session-820308- -
🟢 84% — 30% AI — ✅ All policies passing
-
Jul 6, 2026 14:22 UTC · 16m59s · $0.14 · 292.6k in / 2.6k out · opencode 1.17.13 (deepseek/deepseek-v4-pro)
Change Summary
-
- Amends and force-pushes the existing commit to test opencode session tracking.
- Adds an explanatory comment above
fileWritingToolsin.opencode/plugins/chainloop-trace.ts. - Commits and pushes the comment change, then answers follow-up repository questions.
AI Session Overall Score
-
🟢 84% — Focused debugging session; the only visible code change was a scoped comment update.
AI Session Analysis Breakdown
-
🟢 91% · scope-discipline
-
🟢 AI kept the only edit inside the user-requested plugin file. · High Impact
🟢 88% · alignment
-
🟢 AI only edited the plugin after the user explicitly asked for a comment. · High Impact
🟢 82% · context-and-planning
-
🟢 AI inspected repo state before amending and diagnosing the hook flow. · Medium Impact
🟡 72% · user-trust-signal
-
🟠 The user showed mild impatience when the amend test did not immediately explain the missing linkage. · Medium Severity
💡 When debugging stalls, stop after one nudge and ask the AI for concrete evidence before continuing.
abstained · solution-quality
-
🟡 Solution quality was not assessed because the visible change was an explanatory comment only. · Low Severity
abstained · verification
-
🟡 Verification was not assessed because the visible change did not alter runtime behavior. · Low Severity
Missing criteria: solution-quality, verification
-
File Attribution
██████░░░░░░░░░░░░░░30% AI / 70% HumanStatus Attribution File Lines modified ai .opencode/plugins/chainloop-trace.ts+64 / -22 created human .cursor/hooks.json+23 / -0
Policies (4)
Status Policy Material Messages ✅ Passed ai-config-ai-agents-allowedai-coding-session-ses-0c- ✅ Passed ai-config-no-dangerous-commandsai-coding-session-ses-0c- ✅ Passed ai-config-no-secretsai-coding-session-ses-0c- ✅ Passed ai-config-mcp-servers-allowedai-coding-session-ses-0c- -
🟡 82% — 100% AI — ⚠️ 1 policies failing
-
Sep 10, 2026 13:19 UTC · 3h1m50s · $84.20 · 2.6k in / 431.8k out · claude-code 2.1.267 (claude-opus-5)
Change Summary
-
- Adds trace-push session links to CLI logging after successful attestation.
- Reworks agent hooks to surface pending session links through Claude post-tool-use notifications.
- Adds pending-link state handling plus provider wiring for Claude, Cursor, and OpenCode.
- Expands Go tests around URL building, hook logging, handoff delivery, and stale-state recovery.
AI Session Overall Score
-
🟡 82% — Strong verification and quality, but alignment, scope, and user-trust show moderate drift.
AI Session Analysis Breakdown
-
🟢 90% · verification
-
🟢 AI repeatedly ran targeted and full Go tests after each change cluster. · High Impact
🟠 Direct terminal rendering of
systemMessagestayed unconfirmed; validation relied on tests and model relay instead. · Medium Severity💡 For hook UX, capture one real terminal render before calling the delivery path fully verified.
🟢 88% · context-and-planning
-
🟢 User front-loaded the dashboard-url gate and required specs before coding. · High Impact
🟢 86% · solution-quality
-
🟢 Reviewer-found flaws were fixed by reworking state flow, not masking symptoms. · High Impact
🟡 78% · user-trust-signal
-
🟠 User had to restate that the agent itself should surface the link, not just the log wording. · Medium Severity
💡 When intent shifts from phrasing to delivery channel, pause and restate the new goal before continuing.
🟡 74% · alignment
-
🟠 AI said the follow-up would stay off PR 3430, then folded it into that same PR. · Medium Severity
💡 When branch strategy changes, pause and confirm whether the follow-up belongs in the same PR.
🟡 70% · scope-discipline
-
🟠 User-expanded scope from log-line changes to agent notifications, and the same PR absorbed both features. · Medium Severity
💡 When scope grows into a second feature, confirm whether it should land in the same PR before folding it in.
-
File Attribution
████████████████████100% AI / 0% Human
Policies (4, 1 failing)
Status Policy Material Messages ✅ Passed ai-config-ai-agents-allowedai-coding-session-2cee62- ✅ Passed ai-config-no-dangerous-commandsai-coding-session-2cee62- ⚠️ Failedai-config-no-secretsai-coding-session-2cee62- Secret (generic-password) was redacted from session content [turn=1756, source=tool_result, line=1]
- Secret (generic-password) was redacted from session content [turn=1767, source=assistant-text, line=3]
- Secret (generic-password) was redacted from session content [turn=1771, source=assistant-tool_use:Bash, line=1]
- Secret (generic-password) was redacted from session content [turn=1772, source=tool_result, line=4]
- Secret (generic-password) was redacted from session content [turn=1776, source=assistant-tool_use:Write, line=1]
- Secret (generic-password) was redacted from session content [turn=1779, source=assistant-tool_use:Write, line=1]
- Secret (generic-password) was redacted from session content [turn=1800, source=assistant-text, line=6]
- Secret (generic-password) was redacted from session content [turn=34, source=tool_result, line=239]
✅ Passed ai-config-mcp-servers-allowedai-coding-session-2cee62- -
Security Checks — ✅ 9 passing
✅ secret-scan
| Status | Policy | Messages |
|---|---|---|
| ✅ Passed | secrets-detection |
- |
✅ sast-scan
| Status | Policy | Messages |
|---|---|---|
| ✅ Passed | owasp-top10-2025 |
- |
| ✅ Passed | sast |
- |
| ✅ Passed | cwe-top25 |
- |
| ✅ Passed | cwe-top26-40-cusp |
- |
✅ iac-scan
| Status | Policy | Messages |
|---|---|---|
| ✅ Passed | iac-misconfiguration |
- |
PR info
| Status | Policy | Messages |
|---|---|---|
| ✅ Passed | pr-min-approvals |
- |
| ✅ Passed | pr-description-required |
- |
| ✅ Passed | pr-user-story-linked |
- |
⏭️ 2 scans not applied
| Scan | Reason |
|---|---|
vulnerability-scan |
no manifest/lockfile changed |
github-actions-scan |
no workflow files changed |
Powered by Chainloop and Chainloop Trace
There was a problem hiding this comment.
All reported issues were addressed across 6 files
Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Address review feedback on the session-link tests. The logging test now uses an organization that differs from the other fixtures and that changes under path escaping, so the asserted URL proves the link is built from the supplied organization rather than from an incidental value. Add a table test for AttestationResult.GetOrganization covering the nil links in the Status chain, which is the accessor the logging call depends on. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 2cee6265-17c4-487a-b31d-072037bd3bc7
Use "Coding Session Available at <url>" so the line reads as a sentence and the link is the last thing on it, which keeps it clickable in a terminal. The link is inline rather than a structured field, and the session field is dropped on that path because the URL already ends in the session ID. Without a dashboard the line stays a bare confirmation naming the session, since there is no link to point at. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 2cee6265-17c4-487a-b31d-072037bd3bc7
There was a problem hiding this comment.
All reported issues were addressed across 2 files (changes from recent commits).
Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
When a coding agent runs the git push, the pre-push hook's session link is captured into that agent's shell-tool output rather than shown to the user, who is the audience for it. A successful push now records its session links in the trace directory, which survives the post-push state wipe. The next hook after a shell command takes them, hands them to the agent, and clears them, so a link is announced at most once. Claude Code's provider emits the message on both of its delivery channels: one the client prints directly, one the model can repeat. Cursor has no hook after a shell command; OpenCode's payload shape is not verified yet, so both are no-ops for now. Links expire after ten minutes, because a push the user ran in their own terminal leaves a record no agent hook will ever consume. A push driven by `chainloop trace run` records nothing at all: it completes after the wrapped agent has exited, so no hook of that agent can fire again. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 2cee6265-17c4-487a-b31d-072037bd3bc7
There was a problem hiding this comment.
All reported issues were addressed across 11 files (changes from recent commits).
Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
Address review feedback. Reading the recorded links no longer consumes them: an agent with no way to reach the user was clearing the record it could not act on, so the link was lost without anyone seeing it. Providers that cannot reach the user now say so with ErrAnnounceUnsupported rather than reporting success, and the record is cleared only once an agent has had its go at showing it. A delivery that was attempted and failed still consumes, because retrying on every later shell command would nag far longer than one dropped notification costs. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 2cee6265-17c4-487a-b31d-072037bd3bc7
Address review feedback. The push skipped recording session links when the trace-run sentinel was set, but that sentinel is cleared in a deferred call, so a killed run leaves it behind. Every later pre-push attestation in that repository would then silently stop notifying the agent. The caller knows the truth without consulting disk, so `trace run` now says so through a push option instead. Nothing infers it from state that can go stale. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 2cee6265-17c4-487a-b31d-072037bd3bc7
There was a problem hiding this comment.
All reported issues were addressed across 9 files (changes from recent commits).
Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
Address review feedback on the two preceding fixes. ClearPendingLinks no longer returns an error its only caller could act on, matching DeleteFileSnapshot and DeleteShellPreSignature, which are the same kind of single-use payload consumed by a best-effort hook. The notifier then stops inspecting one error either side of a mutation and clears in one place. Record why announcing before clearing is at-least-once by choice: the agent never acknowledges what it rendered, so exactly-once is not available, and the other ordering trades a repeated line for a link nobody sees. Split the keep-the-links case into its own test rather than a table flag that gave one subtest two endings, drop a doc claim about corrupt records that a truncating write already prevents, and remove three restatements of rationale that lives with the field it describes. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 2cee6265-17c4-487a-b31d-072037bd3bc7
There was a problem hiding this comment.
All reported issues were addressed across 7 files (changes from recent commits).
Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
Address review feedback. The test said a capable agent still gets its turn later, but reused the unsupported provider for the second call, so it only proved an unsupported agent is retried and never covered the transition it described. The second call now uses a capable provider and asserts it is offered the links, announces them, and consumes them. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 2cee6265-17c4-487a-b31d-072037bd3bc7
The session-start banner read "This session will be attested by
Chainloop". "Attested" is Chainloop vocabulary, not the developer's: a
teammate who cloned the repository and never ran init cannot tell
whether that means recorded, uploaded or signed. It also never said
where the data goes, which is a poor showing for a tool whose pitch is
transparency about what leaves your machine.
It now reads:
Chainloop Trace is recording this session.
Evidence will be sent to <dashboard URL>
organization: <org> project: <project>
The destination line appears only when the control plane reports a web
dashboard, matching the gate the session link already uses. Finding out
costs one Infoz call, which needs no credentials and is abandoned after
two seconds, because a developer waiting to type is a worse cost than a
missing line. Organization and project come from .chainloop.yml at no
cost. Each line is dropped when its fact is unknown, so the banner never
names a destination it did not confirm.
fetchUIDashboardURL now takes the timeout from its caller, since a
person waiting on a command they ran themselves will tolerate far more
than an agent hook holding up a first prompt.
Assisted-by: Claude Code
Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev>
Chainloop-Trace-Sessions: 2cee6265-17c4-487a-b31d-072037bd3bc7
There was a problem hiding this comment.
All reported issues were addressed across 5 files (changes from recent commits).
Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
The session-start banner spent a line on the destination and another on
the organization and project, though between them they answer a single
question: where this is going. They now share a line:
Chainloop Trace is recording this session.
Evidence will be sent to <url> (organization: <org>, project: <p>)
The space before the parenthesis is load-bearing, since it is what lets
a terminal linkify the URL without swallowing the punctuation after it.
Either half still stands alone when the other is unknown.
Building that banner costs a control-plane round trip, and Cursor and
opencode discard system messages entirely, so every session start on
those agents paid up to two seconds for a string nobody would read.
Providers now report whether the channel reaches the user, and the
lookup is skipped when it does not.
The capability is a predicate rather than a sentinel error because the
cost being avoided is in preparing the argument, which a post-hoc error
cannot save. A registry test fails when a new provider arrives without
a recorded decision, since the zero value would quietly claim no
support.
Assisted-by: Claude Code
Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev>
Chainloop-Trace-Sessions: 2cee6265-17c4-487a-b31d-072037bd3bc7, 82030869-b7f3-4352-bd95-cea2eb75abab
The opencode plugin now traces bash tool invocations in addition to file-writing tools, and resolves every file touched by an apply_patch call instead of only a single filePath/path argument. Hook invocations are fire-and-forget so a missing or failing chainloop binary never blocks tool execution. Also adds .cursor/hooks.json so Cursor sessions emit session-start, session-end, and after-file-edit trace events. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: ses_0c830ab01ffeGQhnPohmihWjP5
There was a problem hiding this comment.
All reported issues were addressed across 2 files (changes from recent commits).
Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
After
chainloop traceattests an AI coding session, the CLI now tells the user where to find it. Two channels, because the push happens in two quite different contexts.Show the session link, note that it shows two because we had an opencode session in the same branch
Closes #3429
AI assistance: this change was produced with Claude Code. Each commit carries an
Assisted-by:trailer.🤖 Posted by Maximus bot (Claude Code) on behalf of @migmartri