Skip to content

feat: add oracle conversation export (read-only ChatGPT conversation archive, Obsidian format) - #402

Open
2nd-Bird wants to merge 2 commits into
steipete:mainfrom
2nd-Bird:upstream-pr/conversation-export
Open

feat: add oracle conversation export (read-only ChatGPT conversation archive, Obsidian format)#402
2nd-Bird wants to merge 2 commits into
steipete:mainfrom
2nd-Bird:upstream-pr/conversation-export

Conversation

@2nd-Bird

@2nd-Bird 2nd-Bird commented Aug 20, 2026

Copy link
Copy Markdown

The problem this solves

People who keep Codex / Claude Code session logs in an Obsidian vault get a compounding knowledge graph out of it. But the years of thinking they did in ChatGPT before agents existed sit outside that graph, and merging it in is miserable: long threads have scrolled out of context, the built-in export is one giant JSON, and copy-pasting hundreds of turns by hand doesn't scale.

Oracle already has the one ingredient needed to fix this — a signed-in ChatGPT Chrome tab reachable over CDP. This PR adds oracle conversation export, which uses that tab read-only to turn an existing conversation into Markdown, one note per query/answer pair, straight into a Git-tracked folder that Obsidian reads as-is. It also works as a plain, faithful ChatGPT archive for people who don't use Obsidian, and it is equally usable from Codex, Claude Code or a shell since it is just a CLI subcommand.

What it does

  • Never sends, clicks or navigates. The only CDP calls are Runtime.evaluate (two same-origin GETs) and close(); a test asserts exactly that, and another greps the in-page expression for click/submit/navigation/non-GET.
  • --source api (default): in the attached tab, fetch('/api/auth/session') → bearer token → fetch('/backend-api/conversation/<id>'). That is the canonical JSON the ChatGPT UI renders from, so branches, create_time, model slugs, canvas (canmore) documents and thoughts-only turns are all visible in one shot, and complete is always true (the current_node path is fully walked). Any signed-in ChatGPT tab is enough — if ref doesn't match a live tab it falls back to any ChatGPT tab.
  • --source dom: the virtualized-scroll DOM crawl with turnIndex gap detection, kept as an explicit legacy fallback (it is structurally blind to thoughts-only turns and can report false "incomplete").
  • --format json (v2 records: one per turn with messageIds, segments, hiddenNodes, attachments, createTime, model, sha256), markdown, raw (untouched backend body), and obsidian (--out <dir>: ChatGPT-<id8>/NNN-YYYY-MM-DD-turn-TTT.md, one byte-exact note per Q/A exchange with query_sha256/answer_sha256 provenance, plus INDEX.md with wikilinks; --timezone, --captured, --folder, --force).
  • --omit-text keeps IDs/hashes only.

The subcommand flag is --source, not --engine, to avoid colliding with the root --engine <api|browser> option. README gains a short section framing the workflow; docs/cli-reference.md documents every flag and the note shape; CHANGELOG updated.

Verification

  • pnpm test: 155 files passed, 1823 tests (62 new across tests/browser/conversationExport.test.ts, conversationApiExport.test.ts, tests/cli/conversationCommand.test.ts, conversationObsidian.test.ts); pnpm typecheck, oxlint, oxfmt --check, pnpm docs:check, pnpm build, git diff --check all green.
  • Real-device smoke on Windows Chrome over CDP against 12 conversations (20 – 562 turns, up to 3 MB of backend JSON): every export complete:true; user and assistant text compared byte-for-byte against the raw backend JSON (e.g. 281/281 queries and 279/279 answers on the largest); no-tab fallback and --format obsidian exercised on the same conversations. The DOM source on the same tab returned partial results with an honest complete:false, which is why api is the default.

Notes for review

  • src/browser/conversationExport.ts dynamic-imports the api module to avoid a static import cycle.
  • The fetch expression is guarded to chatgpt.com / chat.openai.com origins.
  • Happy to split the obsidian format into a follow-up PR if you'd rather land the export core first.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Q2TDk7gPAY1tutSVGkhN32

…ult format)

Add an attach-only `oracle conversation export [ref]` command that reads an
existing ChatGPT conversation through CDP without sending prompts or
navigating.

- Default `--source api`: fetches ChatGPT's canonical
  /backend-api/conversation/<id> JSON from any signed-in tab (bearer token
  from /api/auth/session), so branches, create_time, model slugs, canvas
  documents and thoughts-only turns are all visible in one shot. Any live
  ChatGPT tab is enough; the conversation's own tab is not required.
- `--source dom`: legacy virtualized-scroll DOM crawl with turnIndex gap
  detection, kept for compatibility.
- `--format json|markdown|raw|obsidian`. The obsidian format writes a
  raw-first vault import: one byte-exact Q/A note per exchange with
  sha256 provenance and an INDEX.md with wikilinks, for agents archiving
  conversations into a Git-tracked knowledge repo.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q2TDk7gPAY1tutSVGkhN32
@clawsweeper

clawsweeper Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

@clawsweeper clawsweeper Bot added merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. P1 Urgent regression or broken agent/channel workflow affecting real users now. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Aug 20, 2026
@clawsweeper

clawsweeper Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed August 20, 2026, 6:04 AM ET / 10:04 UTC.

ClawSweeper review

What this changes

This PR adds an Oracle CLI command that reads a signed-in ChatGPT conversation through Chrome DevTools and exports it as normalized JSON, Markdown, raw response data, or Obsidian notes.

Merge readiness

Blocked until real behavior proof is added - 12 items remain

This PR is not merge-ready: its privacy flag can still emit conversation text, its documented vault subfolder can escape the requested output root, and the claimed device smoke has no inspectable artifact.

Priority: P1
Reviewed head: 62bf5e594004f5ba1ad91f2e60e28a1b6a32c8c2
Owner decision: Required. See Decision needed.

Review scores

Measure Result What it means
Overall readiness 🧂 unranked krab (1/6) The feature has substantial test coverage, but the unresolved privacy/output defects and missing real-device artifact make it unready to merge.
Proof confidence 🧂 unranked krab (1/6) Needs real behavior proof before merge: The body reports a device smoke but includes no inspectable after-fix artifact; add a redacted terminal transcript, log, or recording that demonstrates an export and its output. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Patch quality 🦪 silver shellfish (2/6) Security review found an item that needs attention.

Verification

Check Result Evidence
Real behavior Needs proof Needs real behavior proof before merge: The body reports a device smoke but includes no inspectable after-fix artifact; add a redacted terminal transcript, log, or recording that demonstrates an export and its output. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed 5 items Privacy flag is bypassed by raw and Obsidian formats: The command documents --omit-text as omitting text, but the Obsidian path never passes redactText to the exporter, while the raw path requests and renders the untouched response body.
Vault subfolder is not confined: A caller-controlled --folder value is joined directly below --out without rejecting traversal or separators, so ../ can resolve outside the advertised vault root.
Existing browser attachment boundary: Current main’s tab-attachment helper is attributed to Peter Steinberger; recent main history also shows his browser and Chrome-session maintenance.
Findings 3 actionable findings [P1] Honor --omit-text for Obsidian exports
[P1] Reject --omit-text with raw output
[P2] Keep custom vault folders beneath --out
Security Needs attention Constrain the vault output path: A --folder value containing .. is joined without validation, allowing an archive of private conversation data to be written outside the intended --out directory.

How this fits together

Oracle’s browser mode attaches to an already-running signed-in ChatGPT tab over local Chrome DevTools. This new command reads a conversation response from that tab, normalizes its turns, then writes the selected archive format to stdout or local files.

flowchart LR
A[CLI command and conversation reference] --> B[Attached signed-in ChatGPT tab]
B --> C[Same-origin authenticated read]
C --> D[Conversation turn normalizer]
D --> E{Requested output format}
E --> F[Console or file export]
E --> G[Obsidian vault notes]
Loading

Decision needed

Question Recommendation
Should Oracle core own a public archive command that reads an authenticated ChatGPT backend conversation endpoint, once the privacy and path-confinement defects are repaired? Sponsor a constrained core command: Keep the feature in Oracle after fixing the privacy and output-root guarantees and documenting the authenticated backend dependency.

Why: The mechanical defects are clear, but accepting a durable core interface built on an authenticated private backend contract is a product and maintenance commitment that source inspection cannot make.

Before merge

  • Add real behavior proof - Needs real behavior proof before merge: The body reports a device smoke but includes no inspectable after-fix artifact; add a redacted terminal transcript, log, or recording that demonstrates an export and its output. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
  • Honor --omit-text for Obsidian exports (P1) - The Obsidian branch invokes the exporter without redactText, so --omit-text --format obsidian writes the raw query and answer text despite the documented privacy flag. Reject that combination or implement a provenance-only Obsidian renderer and regression test.
  • Reject --omit-text with raw output (P1) - --format raw deliberately emits value.raw, which is the untouched backend response, so combining it with the documented --omit-text option still exposes every message. Fail validation for this incompatible combination before fetching/rendering.
  • Keep custom vault folders beneath --out (P2) - --folder is documented as a vault subfolder, but this join accepts ../ and separator-containing values, allowing writes outside --out. Validate it as one safe path segment and add traversal cases to the writer tests.
  • Resolve security concern: Constrain the vault output path - A --folder value containing .. is joined without validation, allowing an archive of private conversation data to be written outside the intended --out directory.
  • Resolve merge risk (P1) - --omit-text can create a false privacy expectation while raw and Obsidian output retain the private conversation text.
  • Resolve merge risk (P1) - --folder ../… can write outside the --out vault root, including into an empty unintended directory.
  • Resolve merge risk (P1) - The feature introduces a permanent core surface that reads an authenticated, private ChatGPT backend endpoint; maintainers need to confirm that this belongs in Oracle core.
  • Complete next step (P2) - Maintainer product direction is needed for the authenticated backend-archive surface; contributor fixes and real behavior proof are required before merge.
  • Improve patch quality - Resolve the three flag and path-confinement findings with focused regression tests.
  • Improve patch quality - Add a redacted real-device export transcript or recording; remove conversation content, identifiers, endpoints, and credentials.

Findings

  • [P1] Honor --omit-text for Obsidian exports — src/cli/conversationCommand.ts:136-142
  • [P1] Reject --omit-text with raw output — src/cli/conversationCommand.ts:120-124
  • [P2] Keep custom vault folders beneath --out — src/cli/conversationCommand.ts:148-154
  • [medium] Constrain the vault output path — src/cli/conversationCommand.ts:154
Agent review details

Security

Needs attention: The authenticated read stays same-origin, but the output-folder contract currently permits path traversal outside the selected vault root.

Review metrics

Metric Value Why it matters
Patch size 12 files, 3,580 added lines; production/docs +1,757, tests +1,823 This is a substantial new authenticated archive surface, so privacy semantics and supported scope need to be settled before merge.

Merge-risk options

Maintainer options:

  1. Repair privacy and output confinement first (recommended)
    Reject or correctly redact incompatible formats, reject absolute/traversal folder names, and add regression coverage before reconsidering merge.
  2. Pause for core-scope decision
    Pause the PR if maintainers do not want Oracle to own authenticated backend-archive behavior as a supported CLI contract.

Technical review

Best possible solution:

If maintainers sponsor the core command, make text omission unambiguous for every format, constrain vault paths beneath --out, add focused regressions, and provide a redacted real-device export transcript.

Do we have a high-confidence way to reproduce the issue?

Yes, from source: --omit-text --format raw emits the untouched raw body, --omit-text --format obsidian exports unredacted notes, and a --folder containing .. escapes the requested output directory.

Is this the best way to solve the issue?

No; the command needs consistent privacy semantics and output confinement before its broader core-scope decision can be evaluated safely.

Full review comments:

  • [P1] Honor --omit-text for Obsidian exports — src/cli/conversationCommand.ts:136-142
    The Obsidian branch invokes the exporter without redactText, so --omit-text --format obsidian writes the raw query and answer text despite the documented privacy flag. Reject that combination or implement a provenance-only Obsidian renderer and regression test.
    Confidence: 0.99
  • [P1] Reject --omit-text with raw output — src/cli/conversationCommand.ts:120-124
    --format raw deliberately emits value.raw, which is the untouched backend response, so combining it with the documented --omit-text option still exposes every message. Fail validation for this incompatible combination before fetching/rendering.
    Confidence: 0.99
  • [P2] Keep custom vault folders beneath --out — src/cli/conversationCommand.ts:148-154
    --folder is documented as a vault subfolder, but this join accepts ../ and separator-containing values, allowing writes outside --out. Validate it as one safe path segment and add traversal cases to the writer tests.
    Confidence: 0.98

Overall correctness: patch is incorrect
Overall confidence: 0.98

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 083bba7e61f4.

Labels

Label changes:

  • add merge-risk: 🚨 compatibility: The documented --omit-text and --folder contracts do not match the current output behavior.

Label justifications:

  • P1: The advertised text-omission mode can expose private conversation content in two output modes.
  • merge-risk: 🚨 compatibility: The documented --omit-text and --folder contracts do not match the current output behavior.
  • merge-risk: 🚨 security-boundary: The new command reads authenticated conversation data and permits a vault-folder value to escape its intended output root.
  • rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🧂 unranked krab and patch quality is 🦪 silver shellfish.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: The body reports a device smoke but includes no inspectable after-fix artifact; add a redacted terminal transcript, log, or recording that demonstrates an export and its output. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Evidence

Security concerns:

  • [medium] Constrain the vault output path — src/cli/conversationCommand.ts:154
    A --folder value containing .. is joined without validation, allowing an archive of private conversation data to be written outside the intended --out directory.
    Confidence: 0.98

What I checked:

  • Privacy flag is bypassed by raw and Obsidian formats: The command documents --omit-text as omitting text, but the Obsidian path never passes redactText to the exporter, while the raw path requests and renders the untouched response body. (src/cli/conversationCommand.ts:120, 62bf5e594004)
  • Vault subfolder is not confined: A caller-controlled --folder value is joined directly below --out without rejecting traversal or separators, so ../ can resolve outside the advertised vault root. (src/cli/conversationCommand.ts:154, 62bf5e594004)
  • Existing browser attachment boundary: Current main’s tab-attachment helper is attributed to Peter Steinberger; recent main history also shows his browser and Chrome-session maintenance. (src/browser/liveTabs.ts:523, 6b17e6db0cae)
  • Real behavior proof is still absent: The PR body describes a Windows smoke, but supplies no redacted terminal output, logs, recording, or linked artifact showing the after-fix export path.
  • Release/main check: The PR head is not contained by a release tag; this is new branch work, not functionality already shipped on main. (62bf5e594004)

Likely related people:

  • Peter Steinberger: Current-main blame attributes the central existing tab-attachment helper to him, and recent history includes Chrome/browser routing maintenance. (role: recent browser and CLI area contributor; confidence: high; commits: 6b17e6db0cae, 39b0d416a937, 3a185f55918a; files: src/browser/liveTabs.ts, bin/oracle-cli.ts)

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

History

Review history (1 earlier review cycle)
  • reviewed 2026-08-20T09:52:13.316Z sha 0d9c599 :: needs real behavior proof before merge. :: [P1] Honor --omit-text in Obsidian exports | [P1] Reject --omit-text with raw output | [P2] Keep custom vault folders beneath --out | [P2] Preserve the actual response for --format raw

…h note headings

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q2TDk7gPAY1tutSVGkhN32
@clawsweeper clawsweeper Bot added the merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. label Aug 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. P1 Urgent regression or broken agent/channel workflow affecting real users now. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant