Skip to content

feat(parser): support Cline CLI sessions - #1722

Open
brynjmsdlnn wants to merge 7 commits into
kenn-io:mainfrom
brynjmsdlnn:feat/cline-provider
Open

feat(parser): support Cline CLI sessions#1722
brynjmsdlnn wants to merge 7 commits into
kenn-io:mainfrom
brynjmsdlnn:feat/cline-provider

Conversation

@brynjmsdlnn

@brynjmsdlnn brynjmsdlnn commented Sep 11, 2026

Copy link
Copy Markdown

Adds discovery, synchronization, and parsing support for Cline CLI task sessions stored in ~/.cline/data/sessions/.

What it does

  • Discovers task sessions from <sessionId>/<sessionId>.json (metadata, exit code) and <sessionId>/<sessionId>.messages.json (transcript), skipping dot- and underscore-prefixed directories.
  • Parses user messages, assistant text, extended thinking blocks, tool calls, and tool results (handling both string and array content blocks).
  • Extracts per-message token metrics (inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens) from assistant turns for multi-step breakdown and peak context tracking.
  • Preserves authoritative metadata.totalCost (including explicit zero costs) via non-duplicating aggregate usage events.
  • Normalizes Cline tools (e.g. read_files, replace_in_file) into canonical taxonomy categories.
  • Adds Cline-specific HTTP and SSH sync resolvers that emit curated session files, preserving empty file lists when all sessions are deleted so client mirrors can evict stale files without recursive root archiving.
  • Adds Cline provider configuration (CLINE_DIR env var, cline_dirs config key), UI metadata, and format provenance documentation in docs/internal/session-format-sources.md.

Review hardening

  • Safe SSH & HTTP remote sync: Preserves empty file-scoped targets when all sessions are deleted so client mirrors can authorize eviction without triggering recursive archiving of the root.
  • Path containment & traversal guards: Added strict validation rejecting dot-prefixed directories (.secret), directory separators, and .. traversal in session IDs.
  • Content-block decoding: Preserves raw JSON payloads in ContentRaw while normalizing array-shaped tool_result content blocks.

Tradeoffs and Limits

  • Session discovery targets Cline CLI task directories (~/.cline/data/sessions/<id>/). Cline VS Code extension sessions stored in extension globalStorage are not tracked by this provider.
  • Token metrics are recorded at the turn level when present; aggregate usage events carry authoritative total cost without duplicating tokens.

Where to look

  • internal/parser/cline.go: Session and message parsing, token metrics extraction, authoritative cost preservation, and tool call pairing.
  • internal/parser/cline_provider.go: Directory discovery, path classification, and traversal guards.
  • internal/remotesync/resolve.go: Remote target resolution and empty-root retention for stale eviction.
  • internal/ssh/resolve.go: SSH resolution script emitting curated session files and safe root markers.
  • docs/internal/session-format-sources.md: Format provenance and upstream Cline schema evidence.

@roborev-ci

roborev-ci Bot commented Sep 11, 2026

Copy link
Copy Markdown

roborev: Combined Review (248670e)

Verdict: Changes require fixes for 4 findings.

High

  • internal/ssh/resolve.go:446-464: The deprecated SSH resolver reaches Cline's generic av_emit_dir path because only the HTTP resolver was made Cline-aware, causing SSH sync to recursively transfer the entire ~/.cline root instead of only session metadata and transcript files. Add a Cline-specific SSH resolver that emits curated metadata and .messages.json files as agent-scoped files, bypassing the generic directory branch.

    Reported by: codex

Medium

  • internal/parser/cline.go:266-295: When any assistant message has per-turn metrics, the parser skips the aggregate usage event, discarding metadata.totalCost—including an explicit zero—and recomputing costs from catalog rates. Preserve the reported aggregate cost through an authoritative, non-double-counting usage path even when per-message token rows exist.

    Reported by: codex

  • internal/remotesync/resolve.go:1166-1183: Cline is marked as a verbatim file-scoped provider, but stale-file authorization falls through to the RooCode path shape, rejecting Cline paths such as data/sessions//.json during deletion races. Add Cline-specific session-file shape validation for both configured-root and direct-sessions-root layouts.

    Reported by: codex

  • internal/parser/taxonomy.go:39-49: The full-parser test expects read_files to normalize to Read, but NormalizeToolCategory lacks that alias, so the test fails and these Cline calls are categorized as Other. Add read_files to the read-category aliases and retain the behavioral assertion.

    Reported by: codex


Reviewers: 2 done | Synthesis: codex, 13s | Total: 14m50s

@roborev-ci

roborev-ci Bot commented Sep 11, 2026

Copy link
Copy Markdown

roborev: Combined Review (8dd516e)

Verdict: Changes require fixes for 3 findings.

High

  • internal/ssh/resolve.go:317-323: The SSH resolver can retain a Cline root record when all agent file records are discarded due to an invalid session ID, causing recursive archiving of the entire Cline root and potentially exposing settings, credentials, API keys, and caches. Preserve an explicit empty files[AgentCline] marker whenever a Cline root is emitted, or emit the root only after at least one valid file record succeeds, and add a regression for an unrepresentable session ID.

    Reported by: codex (security)

Medium

  • internal/parser/cline.go:431-435, 541-568: Cline tool results in content-block form are parsed as arrays without result or error fields, leaving result event content empty. ContentRaw is also populated with extracted text instead of raw JSON, violating its downstream contract and preventing decoding. Preserve string(block.Content) in ContentRaw, decode standard type/text blocks for result events, retain error detection, and calculate accurate content lengths.

    Reported by: codex

  • internal/parser/cline_provider.go:134-138: clineFindFile joins caller-provided rawID directly into filesystem paths, allowing IDs containing path traversal to escape the configured Cline sessions directory and return unrelated external files as sources. Reject path separators, . or .., and other invalid ID characters before joining, and verify the resolved metadata path remains under the resolved sessions directory.

    Reported by: codex


Reviewers: 2 done | Synthesis: codex, 11s | Total: 16m40s

@roborev-ci

roborev-ci Bot commented Sep 11, 2026

Copy link
Copy Markdown

roborev: Combined Review (0a0c101)

Verdict: Changes require fixes for 2 findings.

Medium

  • internal/remotesync/resolve.go:97-106; internal/remotesync/types.go:139-148; internal/ssh/resolve.go:300-325: When the final Cline session is deleted, both remote resolvers drop the Cline root instead of retaining an empty file-scoped target, so stale client targets are rejected and old session files cannot be evicted. Treat Cline as an empty file-scoped agent and preserve the existing root with an explicit empty file list in both HTTP and SSH resolution, while avoiding recursive archiving of the root.

    Reported by: codex

  • internal/parser/cline_provider.go:63: Cline discovery skips only underscore-prefixed directories, allowing dot-prefixed directories such as .secret/.secret.json to enter the verbatim remote-sync allowlist and cross the remote-sync boundary. Reject dot-prefixed session directories in Cline discovery and changed-path classification, and add parity coverage for HTTP and SSH target resolution.

    Reported by: codex (security)


Reviewers: 2 done | Synthesis: codex, 11s | Total: 16m51s

@brynjmsdlnn
brynjmsdlnn marked this pull request as draft September 11, 2026 13:49
@brynjmsdlnn
brynjmsdlnn marked this pull request as ready for review September 11, 2026 14:07
@roborev-ci

roborev-ci Bot commented Sep 11, 2026

Copy link
Copy Markdown

roborev: Combined Review (d3a09c0)

Verdict: Changes require fixes for 2 findings.

High

  • internal/remotesync/resolve.go:628-665; internal/ssh/resolve.go:303-319: Cline remote resolvers follow symlinked data or sessions ancestors without verifying physical containment, allowing external matching files to bypass the Cline root allowlist. Reject symlinked ancestors or verify canonical paths remain beneath the configured Cline root before discovering or emitting files.

    Reported by: codex, codex (security)

  • internal/ssh/resolve.go:311-319; internal/ssh/transfer.go:114-127: SSH Cline session IDs are copied into newline-delimited tar input without rejecting backslashes, allowing tar escape processing to introduce path traversal outside the Cline root. Reject backslashes and other tar-list escape characters in Cline IDs, or use NUL-delimited tar input with --null together with physical-containment validation.

    Reported by: codex (security)


Reviewers: 2 done | Synthesis: codex, 9s | Total: 19m29s

Add discovery, parsing, remote sync, and SSH sync support for Cline CLI sessions stored in ~/.cline/data/sessions/. Extracts user and assistant messages, thinking text, tool calls, and per-message token metrics while preserving authoritative total costs, and scopes SSH sync to curated session files.
@roborev-ci

roborev-ci Bot commented Sep 11, 2026

Copy link
Copy Markdown

roborev: Combined Review (d1c8546)

Verdict: Changes require fixes for 1 finding.

Medium

  • internal/parser/cline_provider.go:46-56: When /data/sessions does not exist, clineResolveSessionsDir falls back to scanning the entire configured root as though it were a sessions directory, potentially ingesting unrelated directories and disagreeing with the remote resolver's layout rules. Only treat an explicitly recognized sessions directory as a direct root; otherwise return no sessions until data/sessions exists, or apply the same explicit layout rules consistently in local and remote resolvers.

    Reported by: codex


Reviewers: 2 done | Synthesis: codex, 9s | Total: 14m28s

…line

Enforce physical root containment for Cline data and data/sessions ancestors in HTTP remote sync and SSH resolution. Reject session IDs containing backslashes to prevent tar escape processing, and prevent clineResolveSessionsDir from falling back to scanning the entire configured root when data/sessions is absent.
@roborev-ci

roborev-ci Bot commented Sep 11, 2026

Copy link
Copy Markdown

roborev: Combined Review (291cef0)

Verdict: Changes require fixes for 2 findings.

Medium

  • internal/parser/cline.go:625-635: A final Cline attempt_completion tool call is classified as orphaned before terminal tools are recognized, so successful sessions may be marked TerminationToolCallPending. Handle attempt_completion as an explicit terminal tool before orphan detection and add a completion-transcript behavior test.

    Reported by: codex

  • internal/ssh/resolve.go:300-323: The SSH resolver does not reject a symlink at the configured Cline root or a direct sessions-root symlink, allowing discovery of session files outside the configured root. Reject symlinked Cline and direct sessions roots before discovery, matching resolveClineTarget, and add a root-symlink parity test.

    Reported by: codex


Reviewers: 2 done | Synthesis: codex, 9s | Total: 20m33s

…inked roots

Handle attempt_completion as a terminal tool in classifyClineTermination so completed Cline sessions are marked TerminationClean. Reject symlinks at configured Cline roots and direct sessions roots in both SSH and HTTP remote sync resolvers, with root-symlink parity tests.
@roborev-ci

roborev-ci Bot commented Sep 11, 2026

Copy link
Copy Markdown

roborev: Combined Review (762758c)

Verdict: Changes require fixes for 1 finding.

High

  • internal/ssh/resolve.go:323-326: The SSH resolver checks only [ -f ] before calling av_emit_agent_file; because that helper resolves symlinks with av_phys_file, symlinked metadata or messages files can cause arbitrary files outside the Cline root to be included in the remote sync archive. Reject symlinked leaf files before emitting them, or enforce no-follow behavior and physical containment under the approved Cline session root in the shared emitter.

    Reported by: codex


Reviewers: 2 done | Synthesis: codex, 8s | Total: 16m56s

@roborev-ci

roborev-ci Bot commented Sep 12, 2026

Copy link
Copy Markdown

roborev: Combined Review (556f83a)

Verdict: Changes require fixes for 1 finding.

Medium

  • internal/remotesync/resolve.go:659-668: The Go remote resolver appends discovered Cline paths without rejecting backslashes, unlike the SSH resolver and stale-path validator. On POSIX, a session ID such as sess\escape can be discovered, but archive path normalization converts the backslash to /, causing the transferred path to no longer match the source and potentially storing it under the wrong layout. Reject unsafe session IDs before adding fresh remote targets using the same validator as Cline stale-path and SSH resolution, and add a fresh-target archive test.

    Reported by: codex


Reviewers: 2 done | Synthesis: codex, 9s | Total: 16m45s

@roborev-ci

roborev-ci Bot commented Sep 12, 2026

Copy link
Copy Markdown

roborev: Combined Review (abed419)

Verdict: Changes require fixes for 2 findings.

Medium

  • internal/parser/cline.go:318-329, 470-482: Cline provider metadata is decoded but discarded, so message and aggregate usage events lack ProviderID and pricing may infer the wrong provider. Propagate the parsed provider into message and usage-event ProviderID fields, with coverage for provider-specific models whose IDs do not encode the provider.

    Reported by: codex

  • internal/parser/cline.go:141-167: The transcript path and stored session ID are built from metadata.session_id, allowing stale or malformed IDs to ignore the companion transcript, associate data with the wrong session, or read outside the session directory. Derive the canonical session ID from the metadata file's parent/name, require a nonempty metadata ID to match it, and use only the validated canonical ID for the companion path.

    Reported by: codex


Reviewers: 2 done | Synthesis: codex, 9s | Total: 15m27s

@roborev-ci

roborev-ci Bot commented Sep 12, 2026

Copy link
Copy Markdown

roborev: Combined Review (df809bb)

Verdict: Changes require fixes for 2 findings.

Medium

  • internal/parser/cline.go:470-496: Cline collects thinking text but stores only ordinary text in ParsedMessage.Content, so Markdown exports silently omit Cline reasoning. Inline standardized thinking markers into Content or update the exporter to render ThinkingText explicitly.

    Reported by: codex

  • internal/parser/cline.go:673-681: The terminal-tool shortcut runs before orphan detection and checks only whether the last tool is attempt_completion, allowing assistant messages with earlier unresolved tool calls to be marked clean. Validate all preceding tool calls before treating attempt_completion as terminal, special-casing only the completion call itself as resolved.

    Reported by: codex


Reviewers: 2 done | Synthesis: codex, 8s | Total: 17m29s

@roborev-ci

roborev-ci Bot commented Sep 12, 2026

Copy link
Copy Markdown

roborev: Combined Review (5c52b4f)

Verdict: Changes require fixes for 1 finding.

Medium

  • internal/remotesync/resolve.go:1073: clineSessionFileShape accepts sessions//.json paths outside the configured Cline session directory, allowing remote peers to request unintended files that may contain private data. Authorize stale paths only relative to clineResolveSessionsDir(root): allow data/sessions// for an application root and / only for a direct sessions root; remove the unconditional sessions// form while retaining regular-file and symlink checks.

    Reported by: codex (security)


Reviewers: 2 done | Synthesis: codex, 10s | Total: 10m22s

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.

1 participant