Skip to content

feat(main): opt-in prompt logging#95

Open
antoine-gauvain wants to merge 1 commit into
DEVtheOPS:mainfrom
antoine-gauvain:main
Open

feat(main): opt-in prompt logging#95
antoine-gauvain wants to merge 1 commit into
DEVtheOPS:mainfrom
antoine-gauvain:main

Conversation

@antoine-gauvain

@antoine-gauvain antoine-gauvain commented Jul 3, 2026

Copy link
Copy Markdown

Description

Add the opt-in ability to log prompts.

Type of change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactoring (no functional changes)
  • Chore (dependency updates, etc.)

Checklist

  • I have read the CONTRIBUTING.md document
  • My code follows the style guidelines of this project
  • bun run lint passes with no errors
  • bun run check:jsdoc-coverage passes with no errors
  • bun run typecheck passes with no errors
  • bun test passes with no errors
  • I have added tests that prove my fix is effective or that my feature works (somewhat)
  • I have updated the documentation accordingly
  • My commits follow the Conventional Commits specification

Related issues

None related found

Additional context

N/A

Summary by CodeRabbit

  • New Features

    • Added a configuration/setting to optionally include full prompt text in user_prompt telemetry events.
    • Added OPENCODE_CAPTURE_PROMPT_IN_LOGS to control this behavior (off by default).
  • Bug Fixes

    • user_prompt events now include the full prompt only when prompt capture is enabled.
    • Added clearer startup logging to indicate when full prompt capture is active.
  • Documentation

    • Updated README telemetry and configuration references to describe the new behavior and privacy implications.
  • Tests

    • Extended configuration tests to validate the default and env-driven behavior.

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@antoine-gauvain, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 55 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 204da18e-17a5-4393-be15-bbc13edd1646

📥 Commits

Reviewing files that changed from the base of the PR and between 15bfec8 and 3530cfe.

📒 Files selected for processing (5)
  • AGENTS.md
  • README.md
  • src/config.ts
  • src/index.ts
  • tests/config.test.ts
📝 Walkthrough

Walkthrough

Adds a capturePromptInLogs configuration flag sourced from OPENCODE_CAPTURE_PROMPT_IN_LOGS. When enabled, startup logging and user_prompt events include full prompt text conditionally. Tests and README entries were updated for the new setting.

Changes

Prompt capture config and telemetry

Layer / File(s) Summary
Config field and loading
src/config.ts
PluginConfig and OtelPluginOptions gain capturePromptInLogs, and loadConfig() resolves it from options or OPENCODE_CAPTURE_PROMPT_IN_LOGS.
Prompt logging behavior
src/index.ts
A startup info log is emitted when prompt capture is enabled, and user_prompt log events only include prompt when the flag is on.
Tests and documentation
tests/config.test.ts, README.md
Config tests cover the default and enabled flag values; README documents the log payload change and the environment variable.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers: dialupdisaster

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and clearly describes the main change: making prompt logging opt-in.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@antoine-gauvain antoine-gauvain changed the title feat: opt-in prompt logging feat(main): opt-in prompt logging Jul 3, 2026
@antoine-gauvain antoine-gauvain marked this pull request as ready for review July 3, 2026 19:23
@dialupdisaster

Copy link
Copy Markdown
Contributor

Thanks for adding this. I think the feature is useful, but I want to clarify the behavior before we merge because prompts are already exported today in trace data.

Currently prompt text is attached to OTel spans via OpenInference attributes:

  • src/handlers/session.ts sets input.value and llm.input_messages on run/session spans
  • src/handlers/message.ts sets input.value and llm.input_messages on LLM spans

So this PR does not add prompt capture for the first time. It adds an opt-in copy of the prompt into the user_prompt OTLP log event.

That distinction matters for privacy/security expectations. With this change, OPENCODE_CAPTURE_PROMPT only controls whether prompts appear in logs. It does not prevent prompt text from being exported in traces when traces are enabled.

Could you update the README/startup wording to make that explicit? Something like:

OPENCODE_CAPTURE_PROMPT controls whether full prompt text is also included in user_prompt log events. Prompt text may already be exported as trace span attributes unless traces are disabled with OPENCODE_DISABLE_TRACES.

Also, repo convention is that config values are available both as OPENCODE_* env vars and plugin tuple options with option → env → default precedence. Could you add a matching capturePrompt?: boolean option to OtelPluginOptions, wire it through loadConfig, document it in the plugin options table, and add a small precedence test?

I’m supportive of the feature as log enrichment, but I’d like the naming/docs to avoid implying this is the only control for prompt export.

@antoine-gauvain

Copy link
Copy Markdown
Author

fair point, I'll modify the naming and documentation to make that clearer

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 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 `@src/config.ts`:
- Around line 13-16: The config naming for prompt capture is inconsistent
between the code and docs, so align the feature name across `PluginConfig`, the
env var parsing in the config module, and any related README references. Update
the `capturePromptInLogs`/`OPENCODE_CAPTURE_PROMPT_IN_LOGS` naming to the agreed
`capturePrompt` convention everywhere it is defined or consumed, and make sure
the option-to-env mapping matches what users are told to set.

In `@tests/config.test.ts`:
- Around line 337-469: Add a loadConfig precedence test for capturePromptInLogs
in the loadConfig options describe block, mirroring the existing option-over-env
coverage for enabled, endpoint, and protocol. Verify that when the env var is
set and the option is explicitly provided, the option value wins, using the
loadConfig symbol and the capturePromptInLogs field so the new test stays
aligned with the rest of this suite.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 46a48332-ba0e-4b60-bb07-409fbf68cb40

📥 Commits

Reviewing files that changed from the base of the PR and between 09aeec1 and 15bfec8.

📒 Files selected for processing (4)
  • README.md
  • src/config.ts
  • src/index.ts
  • tests/config.test.ts
✅ Files skipped from review due to trivial changes (1)
  • README.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/index.ts

@coderabbitai coderabbitai 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.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 2

🤖 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 `@src/config.ts`:
- Around line 13-16: The config naming for prompt capture is inconsistent
between the code and docs, so align the feature name across `PluginConfig`, the
env var parsing in the config module, and any related README references. Update
the `capturePromptInLogs`/`OPENCODE_CAPTURE_PROMPT_IN_LOGS` naming to the agreed
`capturePrompt` convention everywhere it is defined or consumed, and make sure
the option-to-env mapping matches what users are told to set.

In `@tests/config.test.ts`:
- Around line 337-469: Add a loadConfig precedence test for capturePromptInLogs
in the loadConfig options describe block, mirroring the existing option-over-env
coverage for enabled, endpoint, and protocol. Verify that when the env var is
set and the option is explicitly provided, the option value wins, using the
loadConfig symbol and the capturePromptInLogs field so the new test stays
aligned with the rest of this suite.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 46a48332-ba0e-4b60-bb07-409fbf68cb40

📥 Commits

Reviewing files that changed from the base of the PR and between 09aeec1 and 15bfec8.

📒 Files selected for processing (4)
  • README.md
  • src/config.ts
  • src/index.ts
  • tests/config.test.ts
✅ Files skipped from review due to trivial changes (1)
  • README.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/index.ts
🛑 Comments failed to post (2)
src/config.ts (1)

13-16: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Env var/option name mismatches README docs.

Code uses capturePromptInLogs / OPENCODE_CAPTURE_PROMPT_IN_LOGS, but the README (per cross-file evidence) documents OPENCODE_CAPTURE_PROMPT for the same feature. Per the PR objectives, the reviewer requested capturePrompt naming for parity with repo conventions, and the author agreed to align naming/docs — this file doesn't yet reflect that update. Users following the README will set the wrong env var and see no effect.

Also applies to: 56-59, 190-190

🤖 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 `@src/config.ts` around lines 13 - 16, The config naming for prompt capture is
inconsistent between the code and docs, so align the feature name across
`PluginConfig`, the env var parsing in the config module, and any related README
references. Update the `capturePromptInLogs`/`OPENCODE_CAPTURE_PROMPT_IN_LOGS`
naming to the agreed `capturePrompt` convention everywhere it is defined or
consumed, and make sure the option-to-env mapping matches what users are told to
set.
tests/config.test.ts (1)

337-469: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Missing option→env precedence test for capturePromptInLogs.

Every other field in this describe block (enabled, logsEnabled, endpoint, protocol, etc.) has a test confirming the option value overrides the env var. capturePromptInLogs has no equivalent test here — only the default/env-set cases are covered above. This is explicitly called out in the PR objectives as a requested addition.

✅ Suggested test to add
+  test("option capturePromptInLogs overrides an enabling env var", () => {
+    process.env["OPENCODE_CAPTURE_PROMPT_IN_LOGS"] = "1"
+    expect(loadConfig({ capturePromptInLogs: false }).capturePromptInLogs).toBe(false)
+  })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

describe("loadConfig options", () => {
  const vars = [
    "OPENCODE_ENABLE_TELEMETRY",
    "OPENCODE_OTLP_ENDPOINT",
    "OPENCODE_OTLP_PROTOCOL",
    "OPENCODE_OTLP_METRICS_INTERVAL",
    "OPENCODE_OTLP_LOGS_INTERVAL",
    "OPENCODE_METRIC_PREFIX",
    "OPENCODE_OTLP_HEADERS",
    "OPENCODE_RESOURCE_ATTRIBUTES",
    "OPENCODE_OTLP_METRICS_TEMPORALITY",
    "OPENCODE_DISABLE_METRICS",
    "OPENCODE_DISABLE_LOGS",
    "OPENCODE_DISABLE_TRACES",
    "OTEL_EXPORTER_OTLP_HEADERS",
    "OTEL_RESOURCE_ATTRIBUTES",
    "OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE",
  ]
  beforeEach(() => vars.forEach((k) => delete process.env[k]))
  afterEach(() => vars.forEach((k) => delete process.env[k]))

  test("enabled via option without any env var", () => {
    expect(loadConfig({ enabled: true }).enabled).toBe(true)
  })

  test("option enabled:false overrides an enabling env var", () => {
    process.env["OPENCODE_ENABLE_TELEMETRY"] = "1"
    expect(loadConfig({ enabled: false }).enabled).toBe(false)
  })

  test("option logsEnabled:false disables logs", () => {
    expect(loadConfig({ logsEnabled: false }).logsEnabled).toBe(false)
  })

  test("option capturePromptInLogs overrides an enabling env var", () => {
    process.env["OPENCODE_CAPTURE_PROMPT_IN_LOGS"] = "1"
    expect(loadConfig({ capturePromptInLogs: false }).capturePromptInLogs).toBe(false)
  })

  test("option endpoint overrides env var", () => {
    process.env["OPENCODE_OTLP_ENDPOINT"] = "http://from-env:4317"
    expect(loadConfig({ endpoint: "http://from-option:4317" }).endpoint).toBe("http://from-option:4317")
  })

  test("env endpoint used when option is absent", () => {
    process.env["OPENCODE_OTLP_ENDPOINT"] = "http://from-env:4317"
    expect(loadConfig({ metricPrefix: "x." }).endpoint).toBe("http://from-env:4317")
  })

  test("option protocol overrides env var", () => {
    process.env["OPENCODE_OTLP_PROTOCOL"] = "grpc"
    expect(loadConfig({ protocol: "http/protobuf" }).protocol).toBe("http/protobuf")
  })

  test("option intervals override env vars", () => {
    process.env["OPENCODE_OTLP_METRICS_INTERVAL"] = "30000"
    const cfg = loadConfig({ metricsInterval: 15000, logsInterval: 2500 })
    expect(cfg.metricsInterval).toBe(15000)
    expect(cfg.logsInterval).toBe(2500)
  })

  test("invalid option interval falls back to env then default", () => {
    process.env["OPENCODE_OTLP_METRICS_INTERVAL"] = "45000"
    const cfg = loadConfig({ metricsInterval: 0, logsInterval: -1 })
    expect(cfg.metricsInterval).toBe(45000)
    expect(cfg.logsInterval).toBe(5000)
  })

  test("non-number option interval is ignored", () => {
    const cfg = loadConfig({ metricsInterval: "soon" } as unknown as Parameters<typeof loadConfig>[0])
    expect(cfg.metricsInterval).toBe(60000)
  })

  test("null options are handled safely", () => {
    expect(() => loadConfig(null as unknown as Parameters<typeof loadConfig>[0])).not.toThrow()
    expect(loadConfig(null as unknown as Parameters<typeof loadConfig>[0]).endpoint).toBe("http://localhost:4317")
  })

  test("option metricPrefix overrides env var", () => {
    process.env["OPENCODE_METRIC_PREFIX"] = "env."
    expect(loadConfig({ metricPrefix: "claude_code." }).metricPrefix).toBe("claude_code.")
  })

  test("option otlpHeaders is copied to OTEL_EXPORTER_OTLP_HEADERS", () => {
    const cfg = loadConfig({ otlpHeaders: "api-key=opt" })
    expect(cfg.otlpHeaders).toBe("api-key=opt")
    expect(process.env["OTEL_EXPORTER_OTLP_HEADERS"]).toBe("api-key=opt")
  })

  test("option resourceAttributes is copied to OTEL_RESOURCE_ATTRIBUTES", () => {
    const cfg = loadConfig({ resourceAttributes: "team=platform" })
    expect(cfg.resourceAttributes).toBe("team=platform")
    expect(process.env["OTEL_RESOURCE_ATTRIBUTES"]).toBe("team=platform")
  })

  test("option metricsTemporality is normalized and copied to OTEL preference", () => {
    const cfg = loadConfig({ metricsTemporality: "delta" })
    expect(cfg.metricsTemporality).toBe("delta")
    expect(process.env["OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE"]).toBe("delta")
  })

  test("invalid protocol option falls back to env then default", () => {
    process.env["OPENCODE_OTLP_PROTOCOL"] = "http/json"
    expect(loadConfig({ protocol: "ftp" as never }).protocol).toBe("http/json")
    delete process.env["OPENCODE_OTLP_PROTOCOL"]
    expect(loadConfig({ protocol: "ftp" as never }).protocol).toBe("grpc")
  })

  test("invalid metricsTemporality option falls back to env then default", () => {
    process.env["OPENCODE_OTLP_METRICS_TEMPORALITY"] = "lowmemory"
    expect(loadConfig({ metricsTemporality: "weekly" as never }).metricsTemporality).toBe("lowmemory")
    delete process.env["OPENCODE_OTLP_METRICS_TEMPORALITY"]
    expect(loadConfig({ metricsTemporality: "weekly" as never }).metricsTemporality).toBeUndefined()
  })

  test("option disabledMetrics array overrides env var", () => {
    process.env["OPENCODE_DISABLE_METRICS"] = "session.count"
    const { disabledMetrics } = loadConfig({ disabledMetrics: ["cache.count", "retry.count"] })
    expect(disabledMetrics).toEqual(new Set(["cache.count", "retry.count"]))
  })

  test("option disabledTraces array expands all to every trace type", () => {
    expect(loadConfig({ disabledTraces: ["all"] }).disabledTraces).toEqual(new Set(TRACE_TYPES))
  })

  test("option disabledTraces array trims and lowercases entries", () => {
    const { disabledTraces } = loadConfig({ disabledTraces: [" LLM ", "Tool"] })
    expect(disabledTraces).toEqual(new Set(["llm", "tool"]))
  })

  test("env values still apply when no options are passed", () => {
    process.env["OPENCODE_ENABLE_TELEMETRY"] = "1"
    process.env["OPENCODE_OTLP_ENDPOINT"] = "http://env:4317"
    const cfg = loadConfig()
    expect(cfg.enabled).toBe(true)
    expect(cfg.endpoint).toBe("http://env:4317")
  })
})
🤖 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 `@tests/config.test.ts` around lines 337 - 469, Add a loadConfig precedence
test for capturePromptInLogs in the loadConfig options describe block, mirroring
the existing option-over-env coverage for enabled, endpoint, and protocol.
Verify that when the env var is set and the option is explicitly provided, the
option value wins, using the loadConfig symbol and the capturePromptInLogs field
so the new test stays aligned with the rest of this suite.

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 participants