From 2a26ac52ff95a462473aabe348aa533a4d7a0328 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 12 Jul 2026 15:05:50 +0000 Subject: [PATCH] feat(report-issue): bundle sanitized data.json in the issue report The Report an Issue bundle now includes a copy of the plugin's persisted settings (data.json), which maintainers often need to reproduce configuration-dependent bugs. The copy is sanitized before it is written: API keys, license keys, tokens, secrets, infrastructure identifiers, userId, every envOverrides value, and legacy enc_* values are masked as [REDACTED], so which providers are configured stays visible while the values never leave the machine. The report dialog, settings description, and docs now surface that the settings copy is included and redacted. Saved as data.json.txt for GitHub's attachment allowlist, mirroring the frame-log workaround. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01W4ZjSKUSbWyWjx4CLeJS5v --- docs/agent-mode-and-tools.md | 7 +- src/agentMode/ui/ReportIssueModal.tsx | 37 ++++- .../v2/components/AdvancedSettings.tsx | 10 +- src/utils/issueReport.test.ts | 40 ++++- src/utils/issueReport.ts | 22 ++- src/utils/sanitizeSettingsData.test.ts | 144 ++++++++++++++++++ src/utils/sanitizeSettingsData.ts | 95 ++++++++++++ 7 files changed, 347 insertions(+), 8 deletions(-) create mode 100644 src/utils/sanitizeSettingsData.test.ts create mode 100644 src/utils/sanitizeSettingsData.ts diff --git a/docs/agent-mode-and-tools.md b/docs/agent-mode-and-tools.md index 2f9ce247..dd251aea 100644 --- a/docs/agent-mode-and-tools.md +++ b/docs/agent-mode-and-tools.md @@ -191,12 +191,13 @@ Clicking it opens a short form where you describe what happened. When you submit 1. Saves a screenshot of the **Agent Mode chat pane** (just the agent panel, not your whole screen). 2. Saves a recent **Agent Mode log** of the behind-the-scenes messages between Copilot and the agent. This log is captured automatically so a report always has recent activity to include; you can turn it off under **Settings → Copilot → Advanced → Keep an Agent Mode activity log**. -3. Opens the folder containing those files in your file manager. -4. Opens a prefilled GitHub issue in your browser. +3. Saves a copy of your **Copilot settings** (the plugin's `data.json`), which helps maintainers reproduce configuration-dependent problems. All secrets — API keys, license keys, tokens, and backend environment variable values — are automatically replaced with `[REDACTED]` before the copy is written. +4. Opens the folder containing those files in your file manager. +5. Opens a prefilled GitHub issue in your browser. The files are **not uploaded for you** — drag them from the opened folder into the GitHub issue to attach them. -> **Privacy note:** The Agent Mode log can contain your prompts, note contents, and tool inputs/outputs in plaintext. Review the saved files before sharing them publicly. +> **Privacy note:** The Agent Mode log can contain your prompts, note contents, and tool inputs/outputs in plaintext, and the settings copy still describes your configuration (model names, folder names, feature toggles) even though secrets are redacted. Review the saved files before sharing them publicly. ### Including the OpenCode log diff --git a/src/agentMode/ui/ReportIssueModal.tsx b/src/agentMode/ui/ReportIssueModal.tsx index f8dab406..df25334f 100644 --- a/src/agentMode/ui/ReportIssueModal.tsx +++ b/src/agentMode/ui/ReportIssueModal.tsx @@ -7,6 +7,7 @@ import { captureViewScreenshot } from "@/utils/captureViewScreenshot"; import { isDesktopRuntime } from "@/utils/desktopRuntime"; import { assembleReportBundle, type ReportEnvInfo } from "@/utils/issueReport"; import { findLatestOpencodeLog } from "@/utils/opencodeLog"; +import { sanitizeSettingsDataForReport } from "@/utils/sanitizeSettingsData"; import { createPluginRoot } from "@/utils/react/createPluginRoot"; import { cn } from "@/lib/utils"; import { getSettings } from "@/settings/model"; @@ -30,6 +31,13 @@ export interface ReportIssueModalParams { activeBackend: string; /** Plugin version for the report's environment block. */ pluginVersion: string; + /** + * Loads the raw persisted plugin settings (the on-disk `data.json`, e.g. via + * `Plugin.loadData()`). The modal sanitizes the result (masks API keys, + * license keys, and other secrets) before bundling it into the report. + * Returns null when the data cannot be loaded; the report then skips it. + */ + loadSettingsData: () => Promise | null>; } interface ElectronShell { @@ -119,6 +127,11 @@ function ReportIssueContent({ showOpencodeOption, onSubmit, onCancel }: ReportIs (not your whole screen) and a recent activity log are saved to a folder on your computer. +
  • + A copy of your Copilot settings (data.json) + is included, with API keys, license keys, and other secrets replaced by{" "} + [REDACTED]. +
  • That folder opens, and a pre-filled GitHub issue opens in your browser.
  • Drag the saved files into the issue to attach them, then submit.
  • @@ -136,8 +149,9 @@ function ReportIssueContent({ showOpencodeOption, onSubmit, onCancel }: ReportIs Before you share these files The activity log can include your prompts, note contents, and tool inputs/outputs in - plain text. Review the saved files and remove anything sensitive before posting them - publicly. + plain text. The settings copy has API keys and other secrets automatically redacted, but + still describes your configuration. Review the saved files and remove anything sensitive + before posting them publicly. @@ -226,6 +240,8 @@ export class ReportIssueModal extends Modal { ? await resolveOpencodeLogPath() : null; + const settingsJson = await this.buildSanitizedSettingsJson(); + const env: ReportEnvInfo = { pluginVersion: this.params.pluginVersion, platform: process.platform, @@ -239,6 +255,7 @@ export class ReportIssueModal extends Modal { screenshotPng, frameLogPath, opencodeLogPath, + settingsJson, reportsRootDir: root, timestamp: formatTimestamp(new Date()), }); @@ -254,6 +271,22 @@ export class ReportIssueModal extends Modal { new Notice("Failed to prepare the issue report. See the console for details."); } } + + /** + * Load the raw persisted settings and return a pretty-printed, sanitized + * JSON string for the bundle, or null when loading fails (the report is + * still produced without it). + */ + private async buildSanitizedSettingsJson(): Promise { + try { + const raw = await this.params.loadSettingsData(); + if (!raw) return null; + return JSON.stringify(sanitizeSettingsDataForReport(raw), null, 2); + } catch (err) { + logError("[ReportIssue] failed to load settings for the report:", err); + return null; + } + } } async function resolveOpencodeLogPath(): Promise { diff --git a/src/settings/v2/components/AdvancedSettings.tsx b/src/settings/v2/components/AdvancedSettings.tsx index 2c9aa30d..2e598ec2 100644 --- a/src/settings/v2/components/AdvancedSettings.tsx +++ b/src/settings/v2/components/AdvancedSettings.tsx @@ -156,6 +156,7 @@ export const AdvancedSettings: React.FC = () => { getPlugin: (id: string) => { manifest?: { version?: string }; agentSessionManager?: { getActiveSession?: () => { backendId?: string } | null }; + loadData?: () => Promise; } | null; }; } @@ -170,6 +171,13 @@ export const AdvancedSettings: React.FC = () => { app, activeBackend, pluginVersion: copilotPlugin?.manifest?.version ?? "unknown", + // Raw loadData() is the true on-disk data.json (the modal sanitizes + // it), so the report reflects what is actually persisted rather than + // the in-memory settings object. + loadSettingsData: async () => { + const raw = await copilotPlugin?.loadData?.(); + return raw && typeof raw === "object" ? (raw as Record) : null; + }, // Resolve at capture time so we can close this Settings window and // reveal the agent pane first — the screenshot should be the chat // surface, not the settings dialog. Null when no agent pane is open. @@ -531,7 +539,7 @@ export const AdvancedSettings: React.FC = () => {