Skip to content

fix(claude): keep ide context out of session titles - #1252

Merged
mjacobs merged 4 commits into
kenn-io:mainfrom
tylergibbs1:agent/filter-claude-ide-context
Jul 24, 2026
Merged

fix(claude): keep ide context out of session titles#1252
mjacobs merged 4 commits into
kenn-io:mainfrom
tylergibbs1:agent/filter-claude-ide-context

Conversation

@tylergibbs1

@tylergibbs1 tylergibbs1 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

the claude code vscode extension writes file-open and selection hints as standalone user-role xml envelopes. agentsview currently treats them as operator prompts, so they can become sidebar titles, count as user turns, and render raw markup.

complete standalone ide_opened_file and ide_selection envelopes now promote to hidden system metadata with distinct subtypes. the match is intentionally strict, so malformed wrappers and messages with real prompt text remain ordinary user content.

dag fork selection applies the same user-text preprocessing before counting turns, so reminder-prefixed ide context cannot promote an obsolete branch. incremental sync keeps promptless ide and continuation appends bounded to the new bytes, falling back to a full parse only when the first title-eligible prompt arrives.

the source data version advances to 74 because main now uses 73 for opencode bash exit-failure detection. existing sqlite archives are reparsed on upgrade, and duckdb mirrors rebuild on their next push through the independent source data-version gate; the mirror schema is unchanged.

closes #1238

@roborev-ci

roborev-ci Bot commented Jul 24, 2026

Copy link
Copy Markdown

roborev: Combined Review (abd35dd)

Code needs one medium-severity fix in Claude DAG branch selection; no security issues were found.

Medium

  • internal/parser/claude.go:1999parseDAG calls countUserTurns before IDE records are classified, while countUserTurns counts every record with entryType == "user". Four IDE-context records can exceed forkThreshold, incorrectly promoting an obsolete branch to the main session even though those records are not genuine user turns. Exclude classifiable system-injected user records from branch-turn counting and add a UUID/DAG fork regression test containing IDE-context records.

Reviewers: 2 done | Synthesis: codex, 7s | Total: 3m56s

@mjacobs mjacobs left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks! The standalone-tag classification and the linear/incremental coverage
look good. The DAG branch-selection issue called out above still needs
addressing though: countUserTurns counts IDE-context user records before
they are classified as promoted system metadata, so enough of them can change
the fork heuristic. Please exclude those records from branch-turn counting and
add a UUID/DAG fork regression.

Two version-number requests as well:

1. Please leave DuckDB SchemaVersion at 5.

This PR does not change the mirror schema. A push already rebuilds when the
mirror's recorded agentsview_source_data_version differs from
db.CurrentDataVersion(). The reparse resync also creates a new SQLite
database_id, which independently forces a full rebuild on the next push.

Raising SchemaVersion has an additional effect: a new binary refuses to serve
existing v5 mirrors until their owner pushes again. Without the bump, the mirror
remains usable—with stale titles—until that push. For this metadata/title
correction, the latter is the better tradeoff and matches the mirror's normal
stale-until-next-push behavior.

AGENTS.md currently instructs authors to bump SchemaVersion for source
data-version changes, so you followed the repository guidance correctly. We
are going to narrow that policy so SchemaVersion tracks mirror-shape changes,
while source data-version changes use the existing push-time rebuild gate. I
will handle that documentation change separately; no AGENTS.md change is
needed in this PR.

2. Use dataVersion 71 on the current base.

Data versions are assigned in merge order rather than reserved for in-flight
PRs. If #1166 lands first, this PR rebases and moves to 72; if this PR lands
first, #1166 does that. A gap would be mechanically harmless because the
migration logic is monotonic, but I'd guide away from reserving numbers for
unmerged work.

@tylergibbs1
tylergibbs1 force-pushed the agent/filter-claude-ide-context branch from abd35dd to 11dcec6 Compare July 24, 2026 14:05
@tylergibbs1

Copy link
Copy Markdown
Contributor Author

Addressed the requested changes in 11dcec65:

  • IDE/system-injected user records no longer count toward DAG fork selection.
  • Added a UUID/DAG fork regression with four IDE-context records.
  • Kept DuckDB SchemaVersion at 5.
  • Rebased onto current main and resolved the data-version conflict. Since main now uses data version 71 for the OpenCode change, this Claude parser reparse is 72.

The focused parser and DB regressions pass, and go vet -tags fts5 ./... is clean.

@roborev-ci

roborev-ci Bot commented Jul 24, 2026

Copy link
Copy Markdown

roborev: Combined Review (11dcec6)

The change is generally sound, but one medium-severity incremental-sync issue can leave new sessions untitled indefinitely.

Medium

  • Empty title persists after the first real promptinternal/parser/claude.go:2577, internal/sync/engine.go:8519

    If a session is initially synced with only IDE-context records, it is stored with an empty FirstMessage and zero user turns. When the real prompt is appended, incremental sync remains eligible because its fallback requires UserMsgCount > 0. Since incremental updates never set first_message, the session can retain an empty title indefinitely.

    Update first_message when an incremental batch contains the first real non-system user prompt, using the full parser’s preview rules, or force a full parse. Add an integration test where IDE context is synced before the real prompt.


Reviewers: 2 done | Synthesis: codex, 10s | Total: 4m21s

@tylergibbs1

Copy link
Copy Markdown
Contributor Author

Addressed the incremental-sync edge case in c812905e. Claude sessions with an empty stored preview now fall back to a full parse even when user_message_count is 0, covering sessions that initially contain only injected IDE context. Added TestIncrementalSync_ClaudeIDEContextOnlyRepairedOnAppend to verify that appending the first real prompt populates first_message and increments the real user count.

Validation:

  • focused clear-only + IDE-context incremental regressions pass
  • CGO_ENABLED=1 go vet -tags fts5 ./... passes
  • full internal/sync run reaches an unrelated existing macOS resync-close test failure (TestResyncAbortsWhenReplacementCloseFails); the failing test also fails in isolation and does not overlap this change.

@roborev-ci

roborev-ci Bot commented Jul 24, 2026

Copy link
Copy Markdown

roborev: Combined Review (c812905)

Code changes look clean; no Medium, High, or Critical findings were reported.


Reviewers: 2 done | Synthesis: codex, 5s | Total: 4m25s

@tylergibbs1
tylergibbs1 force-pushed the agent/filter-claude-ide-context branch from c812905 to 0080af1 Compare July 24, 2026 18:43
@tylergibbs1

Copy link
Copy Markdown
Contributor Author

@mjacobs, this is rebased onto current main and ready for re-review. The upstream OpenCode migration remains data version 72; this Claude IDE-context reparse is now version 73. The prior DAG and incremental-title requests remain addressed, parser/database tests and the focused incremental-sync regressions pass, and go vet -tags fts5 ./... is clean.

@roborev-ci

roborev-ci Bot commented Jul 24, 2026

Copy link
Copy Markdown

roborev: Combined Review (0080af1)

Code review found two medium-severity correctness/performance issues; no security concerns were identified.

Medium

  • internal/sync/engine.go:8520 — Appending to a Claude session without a preview forces a full parse, so consecutive IDE-context events before the first prompt repeatedly parse the growing transcript and can cause quadratic background work. Parse the delta first and fall back only when it contains the first eligible user prompt, or support setting FirstMessage incrementally. Add a regression test confirming IDE-only appends remain incremental.

  • internal/parser/claude.go:2013 — Fork counting classifies raw text, while message extraction removes leading <system-reminder> blocks first. Reminder-prefixed IDE context can therefore be stored as system metadata but counted as genuine user turns, allowing four such records to exceed forkThreshold and promote an obsolete branch. Apply preprocessClaudeUserText before classification in isCountedClaudeUserTurn, and add fork coverage for reminder-prefixed IDE context.


Reviewers: 2 done | Synthesis: codex, 8s | Total: 5m3s

tylergibbs1 and others added 4 commits July 24, 2026 13:11
The empty-preview fallback forced a full parse on every append while a
Claude session had no real user prompt yet. A session file created by
resume or auto-compact starts with a promoted continuation record and
can stream assistant work for hours before the next prompt, so each
watcher event re-parsed the whole growing transcript.

Parse the appended chunk first and fall back to a full parse only when
the chunk carries a message that can become first_message: role user,
not system-injected, non-empty, and not a preview-skipped slash command
(IsSkippablePreviewCommand, now exported from the parser). All other
appends stay on the incremental path so per-event work remains bounded
by the appended bytes rather than the transcript size.
@mjacobs
mjacobs force-pushed the agent/filter-claude-ide-context branch from 0080af1 to 5308806 Compare July 24, 2026 20:15
@roborev-ci

roborev-ci Bot commented Jul 24, 2026

Copy link
Copy Markdown

roborev: Combined Review (5308806)

No issues found.


Reviewers: 2 done | Synthesis: codex | Total: 3m9s

@tylergibbs1

Copy link
Copy Markdown
Contributor Author

Both findings from the latest review are addressed on the branch: 2759a93 parses appended Claude deltas before falling back only for the first eligible prompt (with incremental-only continuation/IDE/command regressions), and 5308806 preprocesses reminder-prefixed user text before DAG turn classification. I validated the exact current head with go fmt ./..., CGO_ENABLED=1 go test -tags fts5 ./internal/parser ./internal/db, CGO_ENABLED=1 go test -tags fts5 ./internal/sync, and CGO_ENABLED=1 go vet -tags fts5 ./...; all pass.

@mjacobs

mjacobs commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

thanks! I'm just waiting on the CI tests that are still running.

@mjacobs
mjacobs merged commit 9e5324c into kenn-io:main Jul 24, 2026
13 checks passed
mjacobs pushed a commit to adammw/agentsview that referenced this pull request Aug 1, 2026
The VS Code extension often prepends a <ide_opened_file> or
<ide_selection> wrapper directly onto a real prompt in the same user
entry. PR kenn-io#1252 promoted standalone envelopes to hidden system
metadata, but this mixed case (envelope + real prompt in one message)
falls outside that strict standalone match by design, so the raw
markup stayed in first_message and the visible transcript.

Split a leading IDE-context envelope off from the rest of the
message: the envelope becomes its own hidden system-metadata message
(same ide_opened_file/ide_selection subtype as the standalone case),
and the remaining real prompt becomes an ordinary user message.
first_message and user turn counts are computed from the same
firstMessageAndUserCount pass used before, so they now derive from
the real prompt only.

Bumps the parser data version to 75 so existing rows re-parse.
mjacobs pushed a commit to adammw/agentsview that referenced this pull request Aug 1, 2026
The VS Code extension often prepends a <ide_opened_file> or
<ide_selection> wrapper directly onto a real prompt in the same user
entry. PR kenn-io#1252 promoted standalone envelopes to hidden system
metadata, but this mixed case (envelope + real prompt in one message)
falls outside that strict standalone match by design, so the raw
markup stayed in first_message and the visible transcript.

Split a leading IDE-context envelope off from the rest of the
message: the envelope becomes its own hidden system-metadata message
(same ide_opened_file/ide_selection subtype as the standalone case),
and the remaining real prompt becomes an ordinary user message.
first_message and user turn counts are computed from the same
firstMessageAndUserCount pass used before, so they now derive from
the real prompt only.

Bumps the parser data version to 75 so existing rows re-parse.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Strip out / parse <ide_opened_file>/<ide_selection> in Claude Code via VSCode extension

2 participants