Skip to content

feat(ingest): single-transcript Claude hook fast-path + global --quiet#420

Open
willwashburn wants to merge 5 commits into
mainfrom
claude/submit-pr-open-issue-Szwgd
Open

feat(ingest): single-transcript Claude hook fast-path + global --quiet#420
willwashburn wants to merge 5 commits into
mainfrom
claude/submit-pr-open-issue-Szwgd

Conversation

@willwashburn
Copy link
Copy Markdown
Member

Summary

Closes #375 — restores the 1.x parity gaps that were tractable in 2.x's file-based ingest model.

  • SDK: new ingest_claude_transcript_path(ledger, path, opts) verb that parses one JSONL and persists an EOF cursor so a follow-up ingest_all skips it. ingest_claude_session delegates through it.
  • CLI: burn ingest --hook claude now drives the new fast-path on the payload's transcript_path, bounding per-hook cost to a single JSONL parse. Falls back to a full sweep when the payload omits the path.
  • CLI: --quiet is no longer requires = "hook" — accepted in default (one-shot) and --watch modes too. One-shot mode still writes its final summary on stdout so pipelines stay capturable; --quiet only suppresses the stderr progress spinner / breadcrumbs / gap warnings.
  • CLI docs: documents the intentional non-port of 1.x's --opencode-stream / --opencode-url / --opencode-global flags. The file-based OpenCode adapter plus the FS-event watch loop is the supported 2.x path; the SDK still ships OpencodeStreamIngestor for embedders that want to consume the SSE feed themselves.

Issue acceptance criteria

  • ☑ Restore OpenCode stream ingest flags or document an intentional replacement → documented in the commands/ingest module doc.
  • ☑ Restore quiet/diagnostic behavior expected by automation → --quiet accepted in every mode.
  • ☑ Add a single-transcript Claude hook fast path → new SDK verb, wired into the hook.
  • ☑ Tests for hook behavior that run deterministically in CI → 2 SDK tests + 7 CLI smoke tests.

Test plan

  • cargo build --workspace
  • cargo test --workspace (all crates green: 22 + 59 + 5 + 31 + 660 + 2 + 13 = 792 tests, plus 3 doctests)
  • New SDK tests: ingest_claude_transcript_path_writes_eof_cursor_so_followup_skips_file, ingest_claude_transcript_path_missing_file_is_empty_report
  • New CLI smoke tests: one-shot stdout summary, --quiet keeps stdout summary, hook rejects unknown harness, hook missing-transcript no-op, hook payload validation, --quiet suppresses payload warning, watch+hook mutex
  • Manual: cargo run -p relayburn-cli -- ingest --quiet (succeeds, writes summary on stdout, no stderr breadcrumbs)

https://claude.ai/code/session_011ubB69Zxijqb1BsYVYL9iQ


Generated by Claude Code

Restores 1.x parity gaps called out in #375:

- `relayburn-sdk`: new `ingest_claude_transcript_path` verb that parses
  just the one JSONL the caller hands it and persists an EOF cursor so a
  follow-up `ingest_all` skips it. `ingest_claude_session` now delegates
  through it.
- `relayburn-cli`: `burn ingest --hook claude` drives the new fast-path
  on the payload's `transcript_path`, bounding per-hook cost to one
  parse. Falls back to a full sweep if the payload omits the path.
- `relayburn-cli`: `--quiet` is no longer hook-only — accepted in
  default (one-shot) and `--watch` modes too. One-shot mode still
  writes its final summary on stdout so pipelines stay capturable.
- `relayburn-cli`: documents the intentional non-port of 1.x's
  `--opencode-stream` / `--opencode-url` / `--opencode-global` flags
  (the file-based OpenCode adapter + FS-event watch is the supported
  2.x path; the SDK still ships `OpencodeStreamIngestor` for embedders
  who want to consume the SSE feed themselves).

Tests:

- SDK: `ingest_claude_transcript_path_*` covers the EOF cursor
  contract and the missing-file no-op policy.
- CLI smoke: one-shot stdout summary, `--quiet` keeps stdout summary,
  hook fast-path no-op on missing transcript, hook payload validation,
  `--quiet` suppresses the hook payload warning, watch+hook mutex.

Closes #375.

https://claude.ai/code/session_011ubB69Zxijqb1BsYVYL9iQ
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 12, 2026

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6f5a9626-bc08-4e63-b6e2-b98acae5bf1b

📥 Commits

Reviewing files that changed from the base of the PR and between 3cb986a and 74edf9f.

📒 Files selected for processing (1)
  • CHANGELOG.md
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md

📝 Walkthrough

Walkthrough

Adds an SDK fast-path to ingest a single Claude transcript file, broadens the CLI --quiet flag to all ingest modes (one-shot/watch/hook) and gates progress UI accordingly, integrates the fast-path into the Claude hook runtime, adds smoke and orchestration tests, and updates README/CHANGELOG.

Changes

Claude Single-Transcript Fast-Path and Quiet Mode Expansion

Layer / File(s) Summary
SDK fast-path implementation & exports
crates/relayburn-sdk/src/ingest/ingest.rs, crates/relayburn-sdk/src/ingest.rs, crates/relayburn-sdk/src/lib.rs
Adds ingest_claude_transcript_path that validates a transcript path, parses the Claude JSONL, appends turns to the ledger, persists an EOF ClaudeCursor, and re-exports the helper; also reformats ingest re-exports.
SDK orchestration tests
crates/relayburn-sdk/src/ingest/orchestration_tests.rs
Two tests validate single-transcript ingestion writes an EOF cursor and that a missing transcript path returns an empty report without error.
CLI flag docs and docs/changelog edits
crates/relayburn-cli/src/cli.rs, README.md, CHANGELOG.md
Removes requires = "hook" from --quiet, updates its help text to apply to all ingest modes, and updates README/CHANGELOG to document the new fast-path and expanded --quiet behavior.
Ingest runtime and watch loop gating
crates/relayburn-cli/src/commands/ingest.rs
Progress creation, task labeling, per-tick ingest options, report/error emission, and teardown are made conditional on args.quiet; one-shot still emits the final stdout summary line.
Claude hook payload validation & fast-path
crates/relayburn-cli/src/commands/ingest.rs, crates/relayburn-sdk/src/ingest/ingest.rs
Hook payload validation now requires session_id and treats transcript_path as optional; runtime calls ingest_claude_transcript_path when transcript_path is present, else falls back to ingest_all.
CLI smoke and validation tests
crates/relayburn-cli/tests/smoke.rs
Smoke tests assert canonical [burn] ingest: ingested prints to stdout (including with --quiet), --quiet suppresses stderr progress UI, unsupported hook harnesses and --watch+--hook parse errors are tested, and payload-missing behaviors are covered.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I munched a JSONL by moonlit code,

One transcript fed, the ledger glowed,
Quiet paws hush the spinner's cheer,
Stdout still sings the summary clear,
SDK and CLI hop on, logs consoled.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: adding a single-transcript Claude hook fast-path and making --quiet a global flag applicable across all ingest modes.
Description check ✅ Passed The description provides comprehensive context about the changes, linking to issue #375, explaining the SDK additions, CLI enhancements, documentation updates, and test coverage.
Linked Issues check ✅ Passed The PR addresses all four acceptance criteria from issue #375: documents the intentional non-port of OpenCode stream flags [#375], enables --quiet across all ingest modes [#375], implements a single-transcript Claude hook fast-path [#375], and adds deterministic CI tests for hook behavior [#375].
Out of Scope Changes check ✅ Passed All changes are directly scoped to issue #375 requirements: SDK adds ingest_claude_transcript_path, CLI adds --quiet support across modes and hook fast-path, tests cover the new behaviors, and docs explain intentional differences. No unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/submit-pr-open-issue-Szwgd

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bf809ad990

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

.get("transcript_path")
.and_then(|x| x.as_str())
.map(PathBuf::from);
if !has_session || transcript.is_none() {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Allow missing transcript_path to hit hook fallback

This guard returns early whenever transcript_path is absent, which makes the later None => ingest_all(...) fallback branch unreachable. In hook mode, payloads that contain a valid session_id but omit transcript_path (the exact older-client case described in this change) will now be ignored instead of triggering a full sweep, so new turns can be missed entirely.

Useful? React with 👍 / 👎.

The previous guard returned 0 whenever transcript_path was absent,
making the documented ingest_all fallback unreachable. Older Claude
Code hook payloads carry session_id without transcript_path; they now
fall through to a full sweep instead of being ignored.

Add a smoke test pinning the new contract: a payload with session_id
but no transcript_path runs under --quiet without stderr noise.

Caught by Codex review on #420.

https://claude.ai/code/session_011ubB69Zxijqb1BsYVYL9iQ
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 2 potential issues.

View 4 additional findings in Devin Review.

Open in Devin Review

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.

🟡 run_watch always creates a progress spinner even when --quiet is passed

The run_watch function unconditionally creates a TaskProgress spinner at line 165 (let progress = TaskProgress::new(globals, "ingest")) and calls progress.set_task(...) on lines 166, 175, 226, and 234 regardless of the quiet flag. Both run_once (line 97) and run_hook (line 342) correctly gate spinner creation with (!quiet).then(|| ...), but run_watch does not. This means burn ingest --watch --quiet still displays a visible spinner on stderr, contradicting the PR's documented behavior ("suppresses stderr progress spinner / breadcrumbs") and the --quiet flag description at crates/relayburn-cli/src/cli.rs:158-163.

(Refers to lines 165-166)

Prompt for agents
In run_watch at crates/relayburn-cli/src/commands/ingest.rs, the progress spinner should be conditionally created based on the quiet flag, matching the pattern used by run_once and run_hook.

The fix requires wrapping the progress in Option<TaskProgress> (like run_once does with (!quiet).then(|| ...)) and guarding all subsequent progress.set_task, progress.finish_and_clear, progress.is_visible, and progress.clone calls behind if-let checks. This is a fairly pervasive change in run_watch since the progress variable is used throughout the function (lines 165-277). The same pattern used in run_once (lines 97-136) and run_hook (lines 342-387) should be followed.

Key locations to update:
- Line 165-166: conditionally create spinner
- Line 170, 175, 182: error handling and set_task calls
- Line 199: is_visible check
- Lines 213, 218: clone for the async block
- Lines 226, 234: set_task inside the ingest loop
- Line 277: finish_and_clear
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +331 to +334
}
return 0;
}
transcript
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.

🔴 Unreachable ingest_all fallback: early return rejects payloads missing transcript_path before the None branch can fire

The run_hook function at crates/relayburn-cli/src/commands/ingest.rs:326 returns early when transcript.is_none(), so transcript_path is always Some(...) after the validation block. This makes the None => ingest_all(...) branch at line 383 dead code. The doc comment (lines 290-294) explicitly states the intent to "fall back to ingest_all when the payload is missing transcript_path (older Claude Code releases occasionally elide it)", but this fallback is never reachable. Payloads from older Claude Code releases that provide session_id but not transcript_path are silently ignored instead of triggering a full sweep.

(Refers to lines 326-334)

Prompt for agents
In run_hook at crates/relayburn-cli/src/commands/ingest.rs, the validation block on lines 319-340 requires both session_id and transcript_path to be present, returning 0 if either is missing. This prevents the None => ingest_all fallback at line 383 from ever executing.

The fix: change the early return condition to only reject when session_id is missing (the truly required field). When session_id is present but transcript_path is absent, the function should proceed with transcript_path as None, which will correctly hit the ingest_all fallback branch.

Specifically, the condition on line 326 should change from:
  if !has_session || transcript.is_none()
to:
  if !has_session

And optionally adjust the eprintln message to only mention session_id.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
crates/relayburn-sdk/src/ingest/ingest.rs (1)

347-353: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t treat every metadata error as “missing file.”

Line 350-Line 353 currently turns all fs::metadata failures into a no-op. That also swallows permission/IO errors and can silently drop ingest work. Keep no-op only for NotFound, and return other errors.

Proposed fix
-    match fs::metadata(file) {
+    match fs::metadata(file) {
         Ok(m) if m.is_file() => {}
         Ok(_) => return Ok(IngestReport::empty()),
-        Err(_) => {
+        Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
             eprintln!("[burn] no session file found at {}", file.display());
             return Ok(IngestReport::empty());
         }
+        Err(err) => return Err(err.into()),
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/relayburn-sdk/src/ingest/ingest.rs` around lines 347 - 353, The
metadata error handling in the fs::metadata(file) match is treating all errors
as “file missing” and silently dropping others; change the Err arm to inspect
the io::ErrorKind: if e.kind() == io::ErrorKind::NotFound then print the message
and return Ok(IngestReport::empty()), otherwise propagate the error
(convert/return the io::Error as the function's error type). Update the match on
fs::metadata(file) (the block using file, IngestReport::empty()) to only
suppress NotFound and return Err for permission/IO errors so legitimate failures
are not swallowed.
crates/relayburn-cli/src/commands/ingest.rs (1)

165-167: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

--watch --quiet still drives progress task updates.

TaskProgress is created and updated in watch mode regardless of quiet. That can leak spinner/breadcrumb output in quiet mode instead of suppressing it.

Also applies to: 226-234

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/relayburn-cli/src/commands/ingest.rs` around lines 165 - 167,
TaskProgress is being created and updated even when the CLI is in quiet mode,
causing spinner/breadcrumb output to leak; modify the ingest command to only
construct and use TaskProgress when quiet is false (e.g., check globals.quiet) —
either by guarding the TaskProgress::new(...) call and all subsequent
progress.set_task(...) / progress.finish() calls (including the other block
around the 226-234 region) with a conditional or by adding a quiet-aware
constructor for TaskProgress and early-return no-op progress operations when
quiet is set; target the TaskProgress variable and all calls like
progress.set_task and progress.finish so no progress updates occur in quiet
mode.
🧹 Nitpick comments (1)
crates/relayburn-cli/tests/smoke.rs (1)

492-503: ⚡ Quick win

Pin quiet-mode stderr suppression in one-shot ingest.

This test currently proves stdout summary retention, but it doesn’t assert that --quiet suppresses stderr breadcrumbs in one-shot mode. Add a stderr-empty assertion so quiet regressions fail deterministically.

Suggested diff
 fn ingest_one_shot_quiet_keeps_stdout_summary() {
     let home = tempfile::TempDir::new().expect("tmp RELAYBURN_HOME");

     burn()
         .args(["ingest", "--quiet"])
         .env("RELAYBURN_HOME", home.path())
         .env("HOME", home.path())
         .env("NO_COLOR", "1")
         .assert()
         .success()
-        .stdout(predicate::str::contains("[burn] ingest: ingested"));
+        .stdout(predicate::str::contains("[burn] ingest: ingested"))
+        .stderr(predicate::str::is_empty());
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/relayburn-cli/tests/smoke.rs` around lines 492 - 503, The test
ingest_one_shot_quiet_keeps_stdout_summary must also assert that stderr is empty
when --quiet is used; update the assert chain on the burn() invocation to
include a stderr assertion (e.g., .stderr(predicate::str::is_empty())) so the
test fails if one-shot quiet mode emits breadcrumbs to stderr while still
checking stdout contains the ingest summary.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Around line 9-13: Remove the issue closure text from the changelog entry: edit
the `relayburn-sdk` bullet that documents `ingest_claude_transcript_path(ledger,
path, opts)` (the per-transcript Claude fast-path used by `burn ingest --hook
claude`) and delete the trailing "Closes `#375`." so the bullet remains
impact-first with no issue/PR reference.

In `@crates/relayburn-cli/src/commands/ingest.rs`:
- Around line 319-333: The current block around the transcript_path check
returns early when transcript_path is missing, making the older-payload fallback
that calls ingest_all unreachable; instead, when has_session is true but
transcript is None, call ingest_all (the existing ingest_all function) and
return its result (respecting the quiet flag for any eprintln) rather than
returning 0. Locate the match/Ok branch that defines has_session and transcript
(variables transcript_path, has_session) and replace the early return with logic
that invokes ingest_all(...) for the full-sweep fallback, keeping the existing
logging behavior.

---

Outside diff comments:
In `@crates/relayburn-cli/src/commands/ingest.rs`:
- Around line 165-167: TaskProgress is being created and updated even when the
CLI is in quiet mode, causing spinner/breadcrumb output to leak; modify the
ingest command to only construct and use TaskProgress when quiet is false (e.g.,
check globals.quiet) — either by guarding the TaskProgress::new(...) call and
all subsequent progress.set_task(...) / progress.finish() calls (including the
other block around the 226-234 region) with a conditional or by adding a
quiet-aware constructor for TaskProgress and early-return no-op progress
operations when quiet is set; target the TaskProgress variable and all calls
like progress.set_task and progress.finish so no progress updates occur in quiet
mode.

In `@crates/relayburn-sdk/src/ingest/ingest.rs`:
- Around line 347-353: The metadata error handling in the fs::metadata(file)
match is treating all errors as “file missing” and silently dropping others;
change the Err arm to inspect the io::ErrorKind: if e.kind() ==
io::ErrorKind::NotFound then print the message and return
Ok(IngestReport::empty()), otherwise propagate the error (convert/return the
io::Error as the function's error type). Update the match on fs::metadata(file)
(the block using file, IngestReport::empty()) to only suppress NotFound and
return Err for permission/IO errors so legitimate failures are not swallowed.

---

Nitpick comments:
In `@crates/relayburn-cli/tests/smoke.rs`:
- Around line 492-503: The test ingest_one_shot_quiet_keeps_stdout_summary must
also assert that stderr is empty when --quiet is used; update the assert chain
on the burn() invocation to include a stderr assertion (e.g.,
.stderr(predicate::str::is_empty())) so the test fails if one-shot quiet mode
emits breadcrumbs to stderr while still checking stdout contains the ingest
summary.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: eac2d015-9a3a-4387-a37c-b9b2993b70f9

📥 Commits

Reviewing files that changed from the base of the PR and between 25c71ae and bf809ad.

📒 Files selected for processing (9)
  • CHANGELOG.md
  • README.md
  • crates/relayburn-cli/src/cli.rs
  • crates/relayburn-cli/src/commands/ingest.rs
  • crates/relayburn-cli/tests/smoke.rs
  • crates/relayburn-sdk/src/ingest.rs
  • crates/relayburn-sdk/src/ingest/ingest.rs
  • crates/relayburn-sdk/src/ingest/orchestration_tests.rs
  • crates/relayburn-sdk/src/lib.rs

Comment thread CHANGELOG.md
Comment thread crates/relayburn-cli/src/commands/ingest.rs
Three reviewer-flagged fixes:

- `relayburn-cli`: `run_watch` now gates the TaskProgress spinner and
  all subsequent set_task / suspend / finish_and_clear calls behind
  Option<TaskProgress>, matching the run_once / run_hook pattern. With
  `--watch --quiet` the spinner is no longer created at all.
- `relayburn-sdk`: `ingest_claude_transcript_path` only suppresses
  NotFound when stat-ing the transcript; permission / IO errors now
  propagate to the caller instead of being silently dropped as an
  empty report.
- `crates/relayburn-cli/tests/smoke.rs`: pin the one-shot quiet
  contract harder — stderr must be empty, not just "stdout still has
  the summary".
- `CHANGELOG.md`: drop the trailing "Closes #375" per the
  impact-first style guide.

https://claude.ai/code/session_011ubB69Zxijqb1BsYVYL9iQ
@willwashburn
Copy link
Copy Markdown
Member Author

@copilot resolve the merge conflicts in this pull request

Resolves conflict with #419 (sync ingest verbs). The new fast-path is
now synchronous like the rest of the ingest surface; the CLI hook
path matches main's non-blocking call sites. Tests converted from
#[tokio::test] back to #[test].

https://claude.ai/code/session_011ubB69Zxijqb1BsYVYL9iQ
Copy link
Copy Markdown
Contributor

Copilot AI commented May 12, 2026

@copilot resolve the merge conflicts in this pull request

Resolved on the branch in 3cb986a. I merged in main, kept the Claude fast-path/--quiet behavior, and updated the ingest code to match main’s synchronous SDK API.

@willwashburn
Copy link
Copy Markdown
Member Author

@copilot resolve the merge conflicts in this pull request

Main released 2.8.5 (#419 + perf/parser refactors) which moved its
prior [Unreleased] block into the release section. Reattach this
branch's items (the new ingest_claude_transcript_path verb and the
CLI hook/--quiet behavior) under [Unreleased] above 2.8.5.

https://claude.ai/code/session_011ubB69Zxijqb1BsYVYL9iQ
Copy link
Copy Markdown
Contributor

Copilot AI commented May 12, 2026

@copilot resolve the merge conflicts in this pull request

Resolved on the branch in 74edf9f.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2.x gap: restore ingest OpenCode stream and hook parity

3 participants