Skip to content

fix(cursor): resolve model from model_id and transcript fallbacks - #55

Merged
sagnik11 merged 2 commits into
mainfrom
fix/cursor-model-detection
Sep 1, 2026
Merged

fix(cursor): resolve model from model_id and transcript fallbacks#55
sagnik11 merged 2 commits into
mainfrom
fix/cursor-model-detection

Conversation

@sagnik11

@sagnik11 sagnik11 commented Sep 1, 2026

Copy link
Copy Markdown
Member

Summary

  • Cursor preset now reads model_id when model is missing or a placeholder (auto, default, unknown)
  • Falls back to extracting the model from transcript_path JSONL when the hook payload doesn't carry a real model name
  • model_extraction supports CursorJsonl and checks model_id on transcript lines
  • VS Code blame hover/status bar hides cursor/unknown-model (and other */unknown-model fallbacks) instead of showing them literally

Context

Fixes cursor/unknown-model showing in attribution UI when Cursor hooks omit model but include model_id, or when Auto routing sends placeholder values.

Test plan

  • cargo test presets::cursor::tests --lib (18 passed)
  • cargo test model_extraction --lib (20 passed)
  • Make an edit in Cursor Agent mode and confirm hover shows the real model (e.g. claude-opus-4-7) instead of cursor/unknown-model
  • Verify hover still shows tool name when model truly cannot be resolved

Made with Cursor


View code changes stack in Autter

Summary

Summary generated by Autter.
Improve Cursor model attribution by resolving concrete model identifiers from hook payloads and Cursor transcripts, rather than persisting placeholder values such as auto or <synthetic>. The VS Code blame UI also normalizes model display so attributed lines show a useful model name when available.

Changes

  • Update CursorPreset model resolution to:
    • Prefer a non-placeholder model value from Cursor hook data.
    • Fall back to a non-placeholder model_id when model is absent or a placeholder.
    • Extract a usable model from the Cursor JSONL transcript when hook fields do not provide one.
    • Return unknown rather than retaining an unusable placeholder when no concrete model can be resolved.
  • Extend JSONL model extraction to recognize top-level model and model_id fields in addition to existing nested message-model and session model-change formats.
  • Add Cursor preset coverage for concrete model_id fallback, transcript fallback behavior, placeholder handling, and workspace/path normalization cases.
  • Update VS Code blame lens model-name extraction and hover rendering to present resolved model metadata consistently.

Acceptance Criteria

  • Cursor preToolUse and postToolUse checkpoints persist a concrete AgentId.model when the hook supplies model_id, including when model is auto.
  • A usable top-level model or model_id in a Cursor JSONL transcript is detected by the shared stream model extractor.
  • Placeholder values (auto, unknown, and <synthetic>) do not override a concrete hook or transcript model.
  • If neither hook payload nor transcript contains a concrete model, Cursor attribution falls back to unknown rather than persisting a placeholder.
  • VS Code blame decorations and hover content display the resolved model name without changing the autter blame --json contract.

Test Plan

  • Run task fmt.
  • Run task lint.
  • Run task test.
  • Use a Cursor hook payload containing model: "auto" and model_id: "claude-opus-4-7" with no readable transcript; verify generated checkpoint events use claude-opus-4-7.
  • Use a Cursor transcript whose model appears only in top-level model or model_id; verify stream extraction returns that concrete model.
  • Open an attributed Cursor-authored file in the VS Code extension and verify the blame lens/hover displays the resolved model name.

Rollback Plan

  • Revert the changes to src/commands/checkpoint_agent/presets/cursor.rs and src/streams/model_extraction.rs to restore the prior Cursor hook and JSONL model-resolution behavior.
  • Revert the agent-support/vscode/src/blame-lens-manager.ts change if the extension renders unexpected model labels or hover content.
  • Rebuild and redistribute the CLI/VS Code extension through the normal release process; existing attribution data is unaffected because this change only alters model resolution for newly processed checkpoints.

Related Issues

No linked issue was identified.

Written for commit cb4d056. Summary will update on new commits.

Read Cursor's model_id when model is missing, extract model from agent transcripts when hooks only send Auto/default placeholders, and hide tool-scoped unknown-model labels in the VS Code hover.

Co-authored-by: Cursor <cursoragent@cursor.com>

@autter-dev autter-dev Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Autter review in progress — running security, correctness & dependency checks on this PR. Follow live step-by-step progress on the autter/review-gate check in the merge box. Merge is blocked until the gate completes; Autter approves automatically when the review comes back clean, and releases this hold with a neutral review when it finds non-blocking issues.

@autter-dev autter-dev Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Autter posted 1 finding(s) as review threads below (🟠 1). Each carries a copy-paste AI fix prompt.

}
}

hook_model.unwrap_or("unknown").to_string()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 [ai] Placeholder hook model is retained when transcript fallback is unusable — Risk: 73/100

If the hook provides a placeholder model such as auto or unknown and transcript extraction fails or yields another placeholder, the final fallback returns the original hook value. Return "unknown" whenever the selected hook model is a placeholder and no usable transcript model was found.

⚠ Downstream affected — if this fails, it cascades to the usage that depends on this file:

  • Functions/symbols: matching_workspace_root, cursor_file_path_from_tool_input, resolve_repo_cwd, parse, normalize_cursor_path, CursorPreset, BlameLensManager
  • Dependent files: agent-support/vscode/src/extension.ts, crate::authorship::authorship_log_serialization::generate_session_id, crate::authorship::working_log::AgentId, crate::commands::checkpoint_agent::bash_tool::{self, Agent, ToolClass}, crate::error::AutterError, std::collections::HashMap, std::path::PathBuf, super::parse
🛠 AI fix prompt (copy & paste into your coding agent)
Change the final fallback to return `unknown` when the resolved hook model is missing or a placeholder. Preserve real model names from the hook and transcript, but do not return placeholder values as the final model string.

Flagged by Autter security & observability checks.

@autter-dev

autter-dev Bot commented Sep 1, 2026

Copy link
Copy Markdown

🚦 Pre-merge checks · ⚠️ 16 warning, ✅ 154 passed

Needs attention

Check Status Explanation
Removed observability ⚠️ Warning 1 potential issue(s) detected (max risk 42/100): src/streams/model_extraction.rs:15.
Rate limiting not detected ⚠️ Warning 1 potential issue(s) detected (max risk 53/100): src/commands/checkpoint_agent/presets/cursor.rs:49.
Batch size limit not detected ⚠️ Warning 1 potential issue(s) detected (max risk 56/100): src/commands/checkpoint_agent/presets/cursor.rs:559.
Security group open to the internet ⚠️ Warning 1 potential issue(s) detected (max risk 64/100): src/commands/checkpoint_agent/presets/cursor.rs:188.
Missing linked tracker issue ⚠️ Warning 1 potential issue(s) detected (max risk 61/100): agent-support/vscode/src/blame-lens-manager.ts:1297.
Missing CODEOWNERS reviewer approval ⚠️ Warning 3 potential issue(s) detected (max risk 76/100): agent-support/vscode/src/blame-lens-manager.ts:1297, src/commands/checkpoint_agent/presets/cursor.rs:49, src/streams/model_extraction.rs:15.
Source changes without matching tests ⚠️ Warning 5 potential issue(s) detected (max risk 73/100): src/commands/checkpoint_agent/presets/cursor.rs:11, src/streams/model_extraction.rs:15, agent-support/vscode/src/blame-lens-manager.ts:1297, src/commands/checkpoint_agent/presets/cursor.rs:49, src/streams/model_extraction.rs:374.
Generic placeholder identifier in production logic ⚠️ Warning 1 potential issue(s) detected (max risk 43/100): src/streams/model_extraction.rs:387.
Repetitive boilerplate (duplicated block) ⚠️ Warning 1 potential issue(s) detected (max risk 22/100): src/commands/checkpoint_agent/presets/cursor.rs:553.
Overbroad try/catch swallowing all exceptions ⚠️ Warning 2 potential issue(s) detected (max risk 63/100): src/commands/checkpoint_agent/presets/cursor.rs:564, src/streams/model_extraction.rs:379.
Public route touches private/PII data ⚠️ Warning 1 potential issue(s) detected (max risk 52/100): agent-support/vscode/src/blame-lens-manager.ts:1130.
Code correctness issue ⚠️ Warning 2 finding(s) on changed lines.
Simplifiable code ⚠️ Warning 1 finding(s) on changed lines.
Complexity Guard ⚠️ Warning 4 finding(s) on changed lines.
Bundle Size Monitor ⚠️ Warning 3 finding(s) on changed lines.
Release Notes Curator ⚠️ Warning 1 finding(s) on changed lines.
✅ Passed checks (154)
Check Status Explanation
Too many files changed ✅ Passed Changed 3 file(s), within the limit of 50.
Too many lines changed ✅ Passed Changed 209 line(s), within the limit of 1000.
Too many unrelated chapters ✅ Passed 3 chapter(s) detected, within the limit of 6.
Generated files hiding real changes ✅ Passed Generated-file volume (0 lines) does not obscure the 209 hand-written line(s).
Missing PR context ✅ Passed PR context looks sufficient.
Mixed concerns (refactor + behavior change) ✅ Passed This PR is a behavior fix focused on model-resolution logic plus a small UI display adjustment; it does not show a pure refactor/no-op cleanup mixed with a separate behavior change.
Migration + app logic + UI combined in one PR ✅ Passed There are no database migration files in this PR, so it cannot combine migrations with application logic and UI changes.
Sensitive data in logs ✅ Passed No sensitive data in logs issues detected.
Log injection ✅ Passed No log injection issues detected.
Missing audit logging ✅ Passed No missing audit logging issues detected.
Silent exception swallowing ✅ Passed No silent exception swallowing issues detected.
Unhandled promise rejection ✅ Passed No unhandled promise rejection issues detected.
Circuit breaker not detected ✅ Passed No circuit breaker not detected issues detected.
Stack trace leakage ✅ Passed No stack trace leakage issues detected.
Multi-write without detected transaction ✅ Passed No multi-write without detected transaction issues detected.
Possible TOCTOU in critical path ✅ Passed No possible toctou in critical path issues detected.
Idempotency key not detected ✅ Passed No idempotency key not detected issues detected.
Possible non-atomic read-modify-write ✅ Passed No possible non-atomic read-modify-write issues detected.
Optimistic locking not detected ✅ Passed No optimistic locking not detected issues detected.
Rate limiting removed ✅ Passed No rate limiting removed issues detected.
Pagination not detected ✅ Passed No pagination not detected issues detected.
Publicly exposed storage ✅ Passed No publicly exposed storage issues detected.
Over-permissive IAM policy ✅ Passed No over-permissive iam policy issues detected.
Unencrypted storage at rest ✅ Passed No unencrypted storage at rest issues detected.
Infrastructure missing access logging ✅ Passed No infrastructure missing access logging issues detected.
Hardcoded secret in IaC ✅ Passed No hardcoded secret in iac issues detected.
Infrastructure misconfiguration ✅ Passed No infrastructure misconfiguration issues detected.
Deprecated Kubernetes API version ✅ Passed No deprecated kubernetes api version issues detected.
Compound IaC attack chain ✅ Passed No compound iac attack chain issues detected.
Prompt injection risk ✅ Passed No LLM/AI-integration code touched by this diff.
LLM output used in a dangerous sink ✅ Passed No LLM/AI-integration code touched by this diff.
Sensitive data in prompt or system-prompt leakage ✅ Passed No LLM/AI-integration code touched by this diff.
Over-privileged LLM tool / excessive agency ✅ Passed No LLM/AI-integration code touched by this diff.
Missing validation on an LLM-driven decision ✅ Passed No LLM/AI-integration code touched by this diff.
Unbounded LLM usage (denial-of-wallet) ✅ Passed No LLM/AI-integration code touched by this diff.
Table exposed without row-level security ✅ Passed No row-level-security-related code touched by this diff.
Over-broad row-level security policy ✅ Passed No row-level-security-related code touched by this diff.
Code path that bypasses row-level security ✅ Passed No row-level-security-related code touched by this diff.
Privileged database credential reachable from the client ✅ Passed No row-level-security-related code touched by this diff.
Privileged query without row-level scoping ✅ Passed No row-level-security-related code touched by this diff.
Template-default gradient styling ✅ Passed No added frontend pages or design-slop markers in this diff.
Interchangeable AI marketing copy ✅ Passed No added frontend pages or design-slop markers in this diff.
Placeholder content shipped to users ✅ Passed No added frontend pages or design-slop markers in this diff.
Emoji standing in for an icon system ✅ Passed No added frontend pages or design-slop markers in this diff.
Call-to-action that goes nowhere ✅ Passed No added frontend pages or design-slop markers in this diff.
Templated page composition ✅ Passed No added frontend pages or design-slop markers in this diff.
Merge-blocking marker left in the change ✅ Passed No pending-work markers added by this diff.
Known-defect marker shipped in code ✅ Passed No pending-work markers added by this diff.
Untracked TODO without an issue reference ✅ Passed No pending-work markers added by this diff.
Test disabled or left pending ✅ Passed No pending-work markers added by this diff.
PII in logs ✅ Passed No pii in logs issues detected.
PII or internals leaked in error response ✅ Passed No pii or internals leaked in error response issues detected.
PII stored without application-level encryption ✅ Passed No pii stored without application-level encryption issues detected.
User data stored without retention controls ✅ Passed No user data stored without retention controls issues detected.
PII sent to external / cross-border destination ✅ Passed No pii sent to external / cross-border destination issues detected.
Lockfile resolution / integrity tampered ✅ Passed No lockfile resolution / integrity tampered issues detected.
Dependency runs install-time lifecycle script ✅ Passed No dependency runs install-time lifecycle script issues detected.
Possible dependency-confusion attack ✅ Passed No possible dependency-confusion attack issues detected.
Lockfile resolves a dependency the manifest does not declare ✅ Passed No lockfile resolves a dependency the manifest does not declare issues detected.
Checked-in build artefact modified without source change ✅ Passed No checked-in build artefact modified without source change issues detected.
Dockerfile build-step is insecure ✅ Passed No dockerfile build-step is insecure issues detected.
External artefact pulled in without integrity pinning ✅ Passed No external artefact pulled in without integrity pinning issues detected.
Changed export, importer not updated ✅ Passed No changed export with an un-updated importer detected.
Missing security-team review on sensitive path ✅ Passed No missing security-team review on sensitive path issues detected.
Migration missing rollback / down step ✅ Passed No migration missing rollback / down step issues detected.
Frontend importing database client directly ✅ Passed No frontend importing database client directly issues detected.
Route handler bypassing service layer ✅ Passed No route handler bypassing service layer issues detected.
Backend service importing UI module ✅ Passed No backend service importing ui module issues detected.
Cross-context internals import ✅ Passed No cross-context internals import issues detected.
Workspace package rule violation ✅ Passed No workspace package rule violation issues detected.
Inconsistent logging pattern ✅ Passed No inconsistent logging pattern issues detected.
Inconsistent error handling ✅ Passed No inconsistent error handling issues detected.
Endpoint missing input validation ✅ Passed No endpoint missing input validation issues detected.
Multi-write without transaction wrapper ✅ Passed No multi-write without transaction wrapper issues detected.
New feature shipped without feature flag ✅ Passed No new feature shipped without feature flag issues detected.
Module placed in the wrong workspace package ✅ Passed No module placed in the wrong workspace package issues detected.
Direct env-var access bypasses config module ✅ Passed No direct env-var access bypasses config module issues detected.
Hallucinated import (package not installed) ✅ Passed No hallucinated import (package not installed) issues detected.
Nonexistent package (not found in registry) ✅ Passed No nonexistent package (not found in registry) issues detected.
Call to function that does not exist ✅ Passed No call to function that does not exist issues detected.
TODO / FIXME on critical path ✅ Passed No todo / fixme on critical path issues detected.
Comment contradicts or fabricates code behaviour ✅ Passed No comment contradicts or fabricates code behaviour issues detected.
Abstraction defined but never used ✅ Passed No abstraction defined but never used issues detected.
Code style differs from rest of codebase ✅ Passed No code style differs from rest of codebase issues detected.
Established pattern ignored ✅ Passed No established pattern ignored issues detected.
Unhandled edge case (null / empty / zero / boundary) ✅ Passed No unhandled edge case (null / empty / zero / boundary) issues detected.
Doc-copy code with insecure defaults ✅ Passed No doc-copy code with insecure defaults issues detected.
Dead code (defined but never referenced) ✅ Passed No dead code (defined but never referenced) issues detected.
Deprecated API call ✅ Passed No deprecated api call issues detected.
API pattern from wrong library version ✅ Passed No api pattern from wrong library version issues detected.
API endpoint removed ✅ Passed No api endpoint removed issues detected.
HTTP method changed (GET ↔ POST etc.) ✅ Passed No http method changed (get ↔ post etc.) issues detected.
New required field added to request ✅ Passed No new required field added to request issues detected.
Field removed from response schema ✅ Passed No field removed from response schema issues detected.
Response field type changed ✅ Passed No response field type changed issues detected.
HTTP status code changed ✅ Passed No http status code changed issues detected.
Auth requirement added / removed / changed ✅ Passed No auth requirement added / removed / changed issues detected.
Error response shape changed ✅ Passed No error response shape changed issues detected.
Pagination behaviour changed ✅ Passed No pagination behaviour changed issues detected.
Outbound webhook payload schema changed ✅ Passed No outbound webhook payload schema changed issues detected.
GraphQL field removed without deprecation ✅ Passed No graphql field removed without deprecation issues detected.
GraphQL enum value removed ✅ Passed No graphql enum value removed issues detected.
SQL injection ✅ Passed No sql injection issues detected.
Cross-site scripting (XSS) ✅ Passed No cross-site scripting (xss) issues detected.
Path traversal ✅ Passed No path traversal issues detected.
Command injection ✅ Passed No command injection issues detected.
Insecure deserialization ✅ Passed No insecure deserialization issues detected.
Weak cryptography ✅ Passed No weak cryptography issues detected.
Hardcoded secret ✅ Passed No hardcoded secret issues detected.
Insecure randomness for security material ✅ Passed No insecure randomness for security material issues detected.
Unsafe file upload ✅ Passed No unsafe file upload issues detected.
Missing input validation ✅ Passed No missing input validation issues detected.
Unsafe CORS configuration ✅ Passed No unsafe cors configuration issues detected.
Unsafe / open redirect ✅ Passed No unsafe / open redirect issues detected.
Missing CSRF protection ✅ Passed No missing csrf protection issues detected.
Unsafe cookie / session settings ✅ Passed No unsafe cookie / session settings issues detected.
Sensitive data exposure ✅ Passed No sensitive data exposure issues detected.
API key in source ✅ Passed No api key in source detected.
Access token in source ✅ Passed No access token in source detected.
Private key in source ✅ Passed No private key in source detected.
Database connection URL with embedded credentials ✅ Passed No database connection url with embedded credentials detected.
Cloud credential in source ✅ Passed No cloud credential in source detected.
Webhook signing secret in source ✅ Passed No webhook signing secret in source detected.
OAuth client secret in source ✅ Passed No oauth client secret in source detected.
JWT signing secret in source ✅ Passed No jwt signing secret in source detected.
Hardcoded password ✅ Passed No hardcoded password detected.
Auth middleware removed from route ✅ Passed No auth middleware removed from route issues detected.
Route protection changed (protected → public) ✅ Passed No route protection changed (protected → public) issues detected.
Permission / RBAC check removed ✅ Passed No permission / rbac check removed issues detected.
Required role weakened ✅ Passed No required role weakened issues detected.
Admin-only route exposed to lower privilege ✅ Passed No admin-only route exposed to lower privilege issues detected.
Token validation skipped in middleware chain ✅ Passed No token validation skipped in middleware chain issues detected.
JWT verification weakened or changed ✅ Passed No jwt verification weakened or changed issues detected.
Session expiration / TTL changed ✅ Passed No session expiration / ttl changed issues detected.
Password reset flow changed ✅ Passed No password reset flow changed issues detected.
OAuth callback / redirect handling changed ✅ Passed No oauth callback / redirect handling changed issues detected.
Webhook endpoint missing signature verification ✅ Passed No webhook endpoint missing signature verification issues detected.
Frontend performance issue ✅ Passed No additional explanation was reported.
Frontend security issue ✅ Passed No additional explanation was reported.
Frontend correctness issue ✅ Passed No additional explanation was reported.
Accessibility issue ✅ Passed No additional explanation was reported.
Frontend maintainability issue ✅ Passed No additional explanation was reported.
Runtime error risk ✅ Passed No additional explanation was reported.
Resource leak risk ✅ Passed No additional explanation was reported.
Data integrity risk ✅ Passed No additional explanation was reported.
Maintainability issue ✅ Passed No additional explanation was reported.
Co-change coupling ✅ Passed No additional explanation was reported.
Redundant alias / duplicate import ✅ Passed No additional explanation was reported.
Redundant type construct ✅ Passed No additional explanation was reported.
Unnecessary type assertion ✅ Passed No additional explanation was reported.
Module smell ✅ Passed No additional explanation was reported.
Excessive complexity ✅ Passed No additional explanation was reported.
Dead export (no callers) ✅ Passed No additional explanation was reported.
Code duplication / DRY violation ✅ Passed No additional explanation was reported.

This comment is updated automatically whenever Autter reviews a new PR revision.

@autter-dev

autter-dev Bot commented Sep 1, 2026

Copy link
Copy Markdown

🧭 PR hygiene & process suggestions

Autter has 1 suggestion(s) about the shape of this PR (size, scope, reviewability). These are process guidance — not code defects — so they are consolidated here instead of posted as inline comments on individual files.

🟠 Missing test coverage for top-level Cursor model_id extraction — Risk: 58/100

The added Cursor JSONL test only asserts extraction from message.model. Add a case containing a top-level model_id and no nested model field, and assert that extract_model(..., StreamFormat::CursorJsonl, ...) returns that value.

🛠 AI fix prompt (copy & paste into your coding agent)
Add a sibling test covering a Cursor JSONL record whose usable model appears only in top-level `model` or `model_id`, and assert `extract_model(..., StreamFormat::CursorJsonl,...)` returns it. Blast radius — skipping this guardrail cascades to the downstream usage that depends on this file: functions `extract_model_from_jsonl_line`, `extract_model_from_jsonl_head`, `extract_model_from_jsonl_tail`, `test_extract_model_empty_file`, `test_extract_model_missing_file`, `test_extract_model_droid_settings_missing_file`, `BlameLensManager`, `CursorPreset`; dependent files `agent-support/vscode/src/extension.ts`, `crate::streams::sweep::StreamFormat`, `crate::streams::types::StreamError`, `std::fs::File`, `std::io::{BufRead, BufReader, Seek, SeekFrom}`, `std::path::Path`.

Flagged by Autter PR-hygiene checks.


🔇 15 finding(s) suppressed as likely false positives by Autter's verification pass

These were flagged by a detector but a second, full-file verification judged them not to be real issues. Listed here for transparency — review if you disagree.

  • 🟠 Security group open to the internet (risk 64/100) — src/commands/checkpoint_agent/presets/cursor.rs:188 — This Rust change only resolves Cursor hook model attribution from model, model_id, or a local transcript. It creates or configures no cloud infrastructure, security group, network listener, or internet-access rule. The cited security-group claim is inapplicable.
  • 🟠 Batch size limit not detected (risk 56/100) — src/commands/checkpoint_agent/presets/cursor.rs:559 — CursorPreset::parse processes one locally invoked Cursor hook payload and produces exactly one ParsedHookEvent. Although workspace_roots is parsed as an array, it is only used to select a working directory; there is no batch execution, per-item downstream operation, or externally exposed bulk API in this code path.
  • 🟠 Rate limiting not detected (risk 53/100) — src/commands/checkpoint_agent/presets/cursor.rs:49 — This is a local CLI hook parser, not a public API or network-facing endpoint. Each invocation parses a single hook event and returns one event, so rate limiting is not applicable to the changed model-resolution logic.
  • 🟠 Overbroad try/catch swallowing all exceptions (risk 63/100) — src/commands/checkpoint_agent/presets/cursor.rs:564 — The referenced unwraps are in a #[cfg(test)] transcript-fixture setup, where failure intentionally fails the test. Production model resolution does not unwrap transcript extraction; it explicitly handles extract_model errors with if let Ok(Some(model)) and falls back to unknown.
  • 🟡 Repetitive boilerplate (duplicated block) (risk 22/100) — src/commands/checkpoint_agent/presets/cursor.rs:553 — The tests cover distinct resolver precedence paths: model_id when model is absent, a real model preferred over model_id, and model_id selected when model is a placeholder. Their similar hook setup is ordinary focused Rust test structure and is not a correctness or maintainability defect.
  • 🟡 Removed observability (risk 42/100) — src/streams/model_extraction.rs:15 — CursorJsonl was previously handled by the wildcard arm and returned Ok(None), so there was no existing Cursor transcript extraction/observability path to remove. This change adds Cursor JSONL support via the established JSONL-tail extractor, and the added test verifies extraction of message.model.
  • 🟠 Public route touches private/PII data (risk 52/100) — agent-support/vscode/src/blame-lens-manager.ts:1130 — There is no public route or authorization boundary here: this is local VS Code extension UI code consuming local autter blame --json output. The added shouldHideModelName explicitly treats empty, default, auto, unknown, and any tool-scoped */unknown-model value as hidden, producing an empty model display rather than exposing that fallback. extractModelName likewise returns null for `*/unkn
  • 🟠 Code correctness issue (risk 73/100) — src/streams/model_extraction.rs:16 — extract_model_from_jsonl_line explicitly falls back from message.model and top-level model to top-level model_id. Since CursorJsonl is routed through this helper, a Cursor JSONL record containing only a top-level string model_id is returned rather than falling through to None.
  • 🟠 Source changes without matching tests (risk 73/100) — src/commands/checkpoint_agent/presets/cursor.rs:49 — The added tests cover the relevant non-happy fallback behavior: a placeholder model with a concrete model_id and no transcript resolves to model_id, while a placeholder-only model with no transcript resolves to unknown. The implementation also deliberately handles unreadable transcripts through if let Ok(Some(model)), falling back safely to unknown rather than persisting auto.
  • 🔴 helper adds another branch-heavy model-hiding rule set (risk 80/100) — agent-support/vscode/src/blame-lens-manager.ts:1130 — shouldHideModelName is a small, single-use helper that centralizes the status-bar display predicate and adds the required */unknown-model fallback handling. It does not introduce a separate policy surface: extractModelName has intentionally different behavior, mapping default/auto to Cursor for other UI contexts while the status bar hides those placeholders.
  • 🟠 parse body now does model resolution plus event shaping (risk 73/100) — src/commands/checkpoint_agent/presets/cursor.rs:49 — parse delegates model fallback policy to the private resolve_cursor_model helper; it only obtains transcript_path once because that value is also needed later for metadata and stream-source construction. Event shaping remains the established responsibility of the preset parser.
  • 🟠 model_id is still skipped when model is a placeholder (risk 73/100) — src/commands/checkpoint_agent/presets/cursor.rs:198 — The resolver reads model and model_id independently. After rejecting a placeholder model, it immediately returns a non-placeholder model_id before attempting transcript extraction. The provided test test_cursor_model_id_fallback_when_model_is_placeholder covers exactly this case.
  • 🟡 Small local helper for hiding placeholder model names (risk 20/100) — agent-support/vscode/src/blame-lens-manager.ts:1130 — The finding itself establishes that the helper is tiny, local, and introduces neither dependencies nor assets. There is no meaningful bundle-size or frontend-performance defect to report.
  • 🟡 CursorJsonl now reuses the generic JSONL tail extractor (risk 11/100) — src/streams/model_extraction.rs:15 — The flagged change is Rust CLI code and only adds an existing enum variant to an existing JSONL extraction branch. It introduces no JS/CSS bundle dependency or bundled asset, so no bundle-size regression is possible.
  • 🟡 New Cursor model-resolution tests add dev-only code paths (risk 10/100) — src/commands/checkpoint_agent/presets/cursor.rs:553 — The cited additions are under #[cfg(test)] in a Rust test module. They are excluded from normal CLI builds and have no browser or frontend bundle impact.

@autter-dev autter-dev Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Autter completed PR review for #55: 5 finding(s) remain below the merge-blocking bar, so this review stays neutral rather than approving. (Also detected: 22 finding(s) dismissed as likely false positives by verification.) See the findings below; the task checklist follows as the review's final comment.

@autter-dev

autter-dev Bot commented Sep 1, 2026

Copy link
Copy Markdown

🤖 Release Notes Curator

Impact: patch — fixes Cursor model attribution by resolving documented model identifiers and usable transcript data.

Changelog: Fixed Cursor attribution to display the actual model more reliably, including when hooks provide a model ID or transcript-derived model.

Custom agent · runs after review · configured in Autter

@autter-dev autter-dev Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Autter posted 3 finding(s) as review threads below (🔴 1 · 🟠 2). Each carries a copy-paste AI fix prompt.


/// Resolve the model for a Cursor hook: prefer hook `model`/`model_id`, then transcript.
fn resolve_cursor_model(data: &serde_json::Value, transcript_path: Option<&str>) -> String {
let hook_model = parse::optional_str_multi(data, &["model", "model_id"]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 [ai] Fall back to model_id when model is only a placeholder — Risk: 82/100

resolve_cursor_model obtains hook_model with optional_str_multi(["model", "model_id"]), which always returns the first present string. For a documented hook payload containing model: "auto" and a concrete model_id, the placeholder causes the first branch to be skipped, but the helper never considers model_id; if the transcript is unavailable or has not yet been written (normal for preToolUse), it returns auto. CursorPreset::parse then copies that value into AgentId.model for every generated checkpoint event, so the production orchestrator persists the placeholder despite receiving the actual model identifier. This breaks the new fallback contract precisely on the pre-tool call path that needs it.

⚠ Downstream affected — if this fails, it cascades to the usage that depends on this file:

  • Dependent files: src/commands/checkpoint_agent/orchestrator.rs, src/commands/checkpoint_agent/presets/cursor.rs
🛠 AI fix prompt (copy & paste into your coding agent)
Read `model` and `model_id` independently. Return a non-placeholder `model` first, then a non-placeholder `model_id`, then try the transcript, and only then return the available placeholder or `unknown`. Add coverage for `{model: "auto", model_id: "claude-opus-4-7"}` without a transcript.

Flagged by Autter security & observability checks.


/// Resolve the model for a Cursor hook: prefer hook `model`/`model_id`, then transcript.
fn resolve_cursor_model(data: &serde_json::Value, transcript_path: Option<&str>) -> String {
let hook_model = parse::optional_str_multi(data, &["model", "model_id"]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 [ai] Fall back to model_id when model is a placeholder — Risk: 78/100

The resolver uses optional_str_multi with model before model_id, so a payload containing model: "auto" and a concrete model_id selects only auto. Because it is a placeholder, the code then tries the transcript; if the transcript is unavailable, incomplete at preToolUse time, or has no model field, line 208 returns auto rather than the supplied concrete model_id. This is reachable for the documented hook shape that carries both fields, and it defeats the PR's stated model_id fallback. The resulting AgentId is passed into both edit checkpoints and normalization does not treat auto as a placeholder, so it is persisted as the model instead of the known actual model.

⚠ Downstream affected — if this fails, it cascades to the usage that depends on this file:

  • Dependent files: src/commands/checkpoint_agent/presets/cursor.rs, src/commands/checkpoint_agent/orchestrator.rs, src/authorship/working_log.rs
🛠 AI fix prompt (copy & paste into your coding agent)
Resolve `model` and `model_id` independently. Use a non-placeholder `model`; otherwise use a non-placeholder `model_id` before attempting transcript extraction, and only retain a placeholder when neither source produces a real model. Add coverage for `{ model: "auto", model_id: "claude-opus-4-7" }` with no usable transcript.

Flagged by Autter security & observability checks.


/// Resolve the model for a Cursor hook: prefer hook `model`/`model_id`, then transcript.
fn resolve_cursor_model(data: &serde_json::Value, transcript_path: Option<&str>) -> String {
let hook_model = parse::optional_str_multi(data, &["model", "model_id"]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 [ai] Do not let a placeholder model hide a usable model_id — Risk: 68/100

When Cursor emits both model: "auto" (or another placeholder) and a concrete model_id, optional_str_multi returns the first present field at this line. The placeholder is rejected at lines 191-195, but model_id is never considered. If the transcript cannot yet be opened or is incomplete, the extraction failure is deliberately ignored at lines 197-205 and the function returns auto; checkpoint normalization then replaces it with cursor/unknown-model before the request is persisted. This loses the valid model identity precisely on the transient transcript-read failure path, producing a permanently incorrect attribution/analytics record despite the hook having supplied the needed value.

⚠ Downstream affected — if this fails, it cascades to the usage that depends on this file:

  • Dependent files: src/commands/checkpoint_agent/presets/cursor.rs, src/commands/checkpoint_agent/orchestrator.rs, src/authorship/working_log.rs
🛠 AI fix prompt (copy & paste into your coding agent)
Resolve `model` and `model_id` independently. Prefer a non-placeholder `model`, then a non-placeholder `model_id`, then a non-placeholder transcript value, and only then return a placeholder/unknown fallback. Add coverage for `model: auto` plus a concrete `model_id` when the transcript is missing or unreadable.

Flagged by Autter security & observability checks.

@autter-dev autter-dev Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Autter blocked this PR after its agentic checks (build/test/deep scans) completed: 2 confirmed correctness/runtime finding(s). See the findings below and the full PR review for details.

Read model and model_id independently so preToolUse hooks with model: auto
and a concrete model_id keep the real model when no transcript is available.
Return unknown instead of persisting placeholder values when no usable
model source is found.

Co-authored-by: Cursor <cursoragent@cursor.com>

@autter-dev autter-dev Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Autter review in progress — running security, correctness & dependency checks on this PR. Follow live step-by-step progress on the autter/review-gate check in the merge box. Merge is blocked until the gate completes; Autter approves automatically when the review comes back clean, and releases this hold with a neutral review when it finds non-blocking issues.

@autter-dev autter-dev Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Autter completed PR review for #55: 4 finding(s) remain below the merge-blocking bar, so this review stays neutral rather than approving. (Also detected: 25 finding(s) dismissed as likely false positives by verification.) See the findings below; the task checklist follows as the review's final comment.

@autter-dev

autter-dev Bot commented Sep 1, 2026

Copy link
Copy Markdown

🤖 Release Notes Curator

  • Impact: patch — Fixes Cursor model attribution by resolving documented hook fields and transcript fallbacks.
  • Changelog: Improved Cursor AI attribution so commits more reliably display the model used, including when Cursor reports automatic or placeholder model values.

Custom agent · runs after review · configured in Autter

@sagnik11

sagnik11 commented Sep 1, 2026

Copy link
Copy Markdown
Member Author

looks good sorted

@autter-dev autter-dev Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Autter posted 3 finding(s) as review threads below (🟠 3). Each carries a copy-paste AI fix prompt.

.and_then(|v| v.as_str())
.or_else(|| json.get("model").and_then(|v| v.as_str()));
.or_else(|| json.get("model").and_then(|v| v.as_str()))
.or_else(|| json.get("model_id").and_then(|v| v.as_str()));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 [ai] Cursor transcript fallback lets a placeholder model hide a concrete model_id — Risk: 78/100

resolve_cursor_model delegates placeholder hook inputs to extract_model and only accepts a non-placeholder return. The shared JSONL extractor selects message.model first, then top-level model, and only then model_id. Consequently a valid Cursor transcript record containing model: "auto" (or message.model: "auto") alongside model_id: "claude-opus-4-7" returns auto; the resolver rejects it and persists unknown, never considering the concrete ID. This breaks the checkpoint orchestrator's new Cursor model-resolution contract for pre/post hook events that must rely on transcript fallback, losing the model identity in the AgentId sent to downstream checkpoint normalization and authorship logging.

⚠ Downstream affected — if this fails, it cascades to the usage that depends on this file:

  • Dependent files: src/commands/checkpoint_agent/orchestrator.rs, src/commands/checkpoint_agent/presets/cursor.rs, src/streams/model_extraction.rs
🛠 AI fix prompt (copy & paste into your coding agent)
When extracting a CursorJsonl model, evaluate `model` and `model_id` independently and prefer a non-placeholder value. Either make `extract_model` format-aware or have `resolve_cursor_model` inspect Cursor transcript records with Cursor placeholder rules, so `auto`/`default`/`unknown` cannot mask a concrete `model_id`. Add coverage for a transcript-only fallback record with `model: auto` plus `model_id: claude-opus-4-7`.

Flagged by Autter security & observability checks.

.and_then(|m| m.get("model"))
.and_then(|v| v.as_str())
.or_else(|| json.get("model").and_then(|v| v.as_str()));
.or_else(|| json.get("model").and_then(|v| v.as_str()))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 [ai] Do not let a placeholder transcript model hide its concrete model_id — Risk: 78/100

The Cursor resolver invokes this generic extractor only after both hook fields are unusable. For a Cursor transcript record containing model: "auto" and model_id: "claude-opus-4-7", this or_else chain selects the present placeholder at line 107, never evaluates model_id at line 108, and returns auto. resolve_cursor_model then rejects that placeholder (cursor.rs:209) and returns unknown (cursor.rs:215), despite the transcript carrying a concrete model identity. This is reachable on postToolUse when Cursor's hook reports Auto/default and the transcript is the fallback source, so persisted attribution is incorrectly normalized to cursor/unknown-model rather than the actual model.

⚠ Downstream affected — if this fails, it cascades to the usage that depends on this file:

  • Dependent files: src/commands/checkpoint_agent/presets/cursor.rs, src/streams/model_extraction.rs, src/authorship/working_log.rs
🛠 AI fix prompt (copy & paste into your coding agent)
When extracting a Cursor JSONL record, resolve `model` and `model_id` independently: use a non-placeholder `model`; otherwise use a non-placeholder `model_id`. Keep the existing precedence for non-Cursor formats or make placeholder filtering format-aware. Add coverage for a Cursor transcript line with `model: "auto"` and a concrete `model_id`.

Flagged by Autter security & observability checks.

.and_then(|m| m.get("model"))
.and_then(|v| v.as_str())
.or_else(|| json.get("model").and_then(|v| v.as_str()));
.or_else(|| json.get("model").and_then(|v| v.as_str()))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 [ai] Do not let a placeholder transcript model mask model_id — Risk: 74/100

When the hook only supplies a placeholder, resolve_cursor_model intentionally falls back to extract_model; however, JSONL extraction chooses a present top-level model before model_id without applying the placeholder check. A Cursor record containing model: "auto" and model_id: "claude-opus-4-7" therefore returns auto; the resolver rejects it, silently treats transcript extraction as unsuccessful, and returns unknown. The orchestrator then normalizes that value to cursor/unknown-model before the checkpoint is persisted. Thus a transient/pre-tool hook with no usable hook model loses the real transcript model rather than producing an error or retaining the available identity.

⚠ Downstream affected — if this fails, it cascades to the usage that depends on this file:

  • Dependent files: src/streams/model_extraction.rs, src/commands/checkpoint_agent/presets/cursor.rs, src/commands/checkpoint_agent/orchestrator.rs, src/authorship/working_log.rs
🛠 AI fix prompt (copy & paste into your coding agent)
Make Cursor JSONL extraction resolve `model` and `model_id` independently, preferring a non-placeholder `model`, then a non-placeholder `model_id`. Ensure the resolver can still fall back to `unknown` only when neither transcript field is usable, and add coverage for a Cursor JSONL record with `model: auto` plus a concrete `model_id`.

Flagged by Autter security & observability checks.

@sagnik11
sagnik11 merged commit efacd25 into main Sep 1, 2026
2 of 3 checks passed

@autter-dev autter-dev Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Autter blocked this PR after its agentic checks (build/test/deep scans) completed: 2 confirmed correctness/runtime finding(s). See the findings below and the full PR review for details.

@autter-dev

autter-dev Bot commented Sep 1, 2026

Copy link
Copy Markdown

Autter task list

  • @sagnik11 Fix Cursor JSONL model precedence (src/streams/model_extraction.rs, src/commands/checkpoint_agent/presets/cursor.rs) - Update JSONL extraction so placeholder values such as auto, unknown, and <synthetic> are skipped at every candidate level and cannot prevent a later concrete model_id from being selected.
  • @sagnik11 Add regression coverage for placeholder transcript fields (src/streams/model_extraction.rs, src/commands/checkpoint_agent/presets/cursor.rs) - Add Rust tests for Cursor JSONL records with placeholder nested or top-level model values plus concrete top-level model_id, and assert both shared extraction and Cursor checkpoint resolution return the concrete ID.
  • @sagnik11 Cover standalone top-level Cursor model_id extraction (src/streams/model_extraction.rs) - Add a shared extractor test containing only a top-level Cursor model_id and assert extract_model returns it for StreamFormat::CursorJsonl.
  • @sagnik11 Validate CLI and VS Code attribution behavior (src/commands/checkpoint_agent/presets/cursor.rs, src/streams/model_extraction.rs, agent-support/vscode/src/blame-lens-manager.ts) - Run task fmt, task lint, and task test, then manually verify a Cursor hook/transcript fallback persists the resolved model and the VS Code blame lens and hover render that model without changing autter blame --json.
  • @sagnik11 Obtain required CODEOWNERS approvals (src/commands/checkpoint_agent/presets/cursor.rs, src/streams/model_extraction.rs, agent-support/vscode/src/blame-lens-manager.ts) - Request and record CODEOWNER approval for the Cursor checkpoint preset, shared stream extractor, and VS Code blame-lens changes after the fixes and tests are updated.

Generated from PR diff, blast radius, and context.

Issues found

  1. Cursor transcript fallback lets a placeholder model hide a concrete model_id · risk 78/100 · src/streams/model_extraction.rs:108
  2. Do not let a placeholder transcript model hide its concrete model_id · risk 78/100 · src/streams/model_extraction.rs:107
  3. Do not let a placeholder transcript model mask model_id · risk 74/100 · src/streams/model_extraction.rs:107
  4. Missing test coverage for top-level Cursor model_id extraction · risk 58/100 · src/streams/model_extraction.rs:374
  5. Missing CODEOWNERS reviewer approval · risk 49/100 · src/commands/checkpoint_agent/presets/cursor.rs:49
  6. Missing CODEOWNERS reviewer approval · risk 49/100 · agent-support/vscode/src/blame-lens-manager.ts:1297
  7. Missing CODEOWNERS reviewer approval · risk 49/100 · src/streams/model_extraction.rs:15

Also detected but not listed above: 25 finding(s) dismissed as likely false positives by verification — see the Autter review dashboard for their verdicts.

🛠 Fix options

Check one option and Autter will start a fix run for the unresolved issues above.

  • One PR with all unresolved fixes
  • One independent PR per unresolved issue

Checking a box triggers the fix run immediately — Autter comments back with the issues being fixed and the branch created for each.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant