Skip to content

feat(settings): guided issue reporting with upload flow (v4, part 3) - #2755

Draft
Emt-lin wants to merge 5 commits into
v4-previewfrom
issue-report-upload
Draft

feat(settings): guided issue reporting with upload flow (v4, part 3)#2755
Emt-lin wants to merge 5 commits into
v4-previewfrom
issue-report-upload

Conversation

@Emt-lin

@Emt-lin Emt-lin commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #2713. Base is settings-v4-agents-basic; it retargets to
v4-preview automatically once #2713 lands. Merge #2713 first.

Reworks the report flow first shipped in #2614 for
logancyang/obsidian-copilot-preview#155, which is already closed — this is the
follow-on refactor and hardening of the same feature, not a second attempt at
that issue.

⚠️ Do not merge yet. The uploader is a mock
(src/utils/reportUpload.mock.ts) that always succeeds and returns a link on
an RFC-reserved .invalid host. The real Brevilabs endpoint does not exist
yet — see Blocked on below.

Problem

Reporting a bug meant assembling the evidence by hand. #2614 gave the report
a modal, but the user still ended up with a folder: a screenshot, a frame log, a
chat log, each attached separately, and no indication of which ones actually
made it in. Sources that could not be read were dropped silently, so the issue
arrived missing the log the maintainer needed with nothing saying so.

Three concrete defects behind that:

  • chatLogRequest() exported logFileManager without draining the prompt
    recorder first, so the attached chat log stopped one request short of the
    failure being reported — the Settings row it replaced did flush
    (commands/index.ts:544 still does).
  • The email rule in redactLog.ts had an unbounded local part, which backtracks
    over every suffix of a long run of address-legal characters. A pasted token
    supplies exactly that: 27 s for a 256 KB run, versus 31 ms bounded.
  • The report landed in a fixed <tmpdir>/obsidian-copilot/reports. On a machine
    whose temp dir is shared between accounts, that path is someone else's to
    create first, and a symlink planted inside it ahead of time is followed by the
    write that lands there — reproduced locally: victim file overwritten and
    chmod-ed.

Fix

A three-page dialog — describe → review → done — that produces exactly one
copilot-report-<id>.zip and reports a per-source outcome for everything in it
(skipped / failed / truncated), so the list reflects the zip rather than
echoing the user's checkboxes back at them.

Upload replaces the drag hand-off: the bundle is uploaded, the returned link is
written into the prefilled issue body, and the user only presses Submit.
Upload & open issue is a separate click from packing — nothing leaves the
machine until it is pressed. If the upload fails the zip is still on disk, with
Retry upload, Show in folder, or Open issue anyway to attach by hand.

Private per-report directory via mkdtemp rather than the fixed path, chosen
over hardening the fixed one: it creates atomically, names unpredictably, and is
owner-only from the start, which settles squatting, prediction, and permissions
in one call instead of three guards.

The linked issue URL puts the share link in a non-truncatable prefix and shrinks
only the body beneath it, so an over-long note can never cost the reader the
link.

Scope

21 files changed, +5206 / −465 relative to #2713. About 2,300 of those additions
are tests.

Changes

  • utils/issueReport.ts — bundle assembly, per-source outcomes,
    budget/limit enforcement, redaction of everything reaching report.md or the
    issue URL, and buildLinkedReportIssueUrl. Injectable Node runtime, so it is
    unit-testable without a filesystem.
  • agentMode/ui/ReportIssueFlow.tsx (new) — the three pages and one
    mutually-exclusive state machine; rebuild and upload each lock the other out
    in both directions via a shared synchronous operation lock.
  • agentMode/ui/ReportIssueModal.tsx — capture, log collection, private
    temp dir, cleanup, upload orchestration. waitForStableTarget polls geometry
    on the target's own window rather than the main window's frame clock, which
    never fires when Obsidian is backgrounded.
  • utils/reportUpload.ts (new) — the ReportUploader contract, kept
    separate from both issueReport.ts and the mock so swapping in a real adapter
    touches one new file.
  • utils/redactLog.ts — RFC-bounded quantifiers on the email rule.
  • settings/v2/components/AdvancedSettings.tsx — the report entry point
    under Debugging & support, injecting the uploader.
  • agentMode/ui/ReportIssueFlow.stories.tsx (new) — gallery stories for all
    six load-bearing states (details, packing, review, uploading, upload-failed,
    done), per the component-gallery workflow in TESTING_GUIDE.md.

Blocked on

The real upload endpoint. When it is built, note that makeFormDataRequest
unconditionally attaches an Authorization header even with skipLicenseCheck
(brevilabsClient.ts:222,243), so the adapter will need an excludeAuthHeader
path. Retry idempotency and completion ambiguity ("failed but the server
actually succeeded") are contract properties the endpoint must provide; a mock
cannot demonstrate them.

Verification

  • npm run test — 396 suites, 5607 tests, all passing (3 suites / 115 tests
    more than feat(settings): fold Agents into Basic (v4, part 3) #2713, which is exactly this PR's coverage).
  • npx tsc --noEmit, npm run lint, npm run format:check — clean.
  • Symlink attack reproduced against the old fixed path (victim overwritten and
    chmod-ed), then confirmed closed: the squatted directory is no longer used and
    the actual path is unpredictable and 0700.
  • ReDoS: 40,000-character pathological input drops to 4 ms bounded; verified
    redaction is not weakened (an over-length local part still has its @ and
    domain replaced).
  • Permission behavior verified on a real filesystem under umask 000, 022, and
    077; POSIX-only tests skip on win32.

Not verified: a real upload. The mock always succeeds, so the failure paths are
covered by injected rejecting uploaders in tests rather than by a live endpoint.

Deferred

session/debugSink.ts writes full prompt frames to a predictable
<tmpdir>/obsidian-copilot/acp-frames/<hash>/ with default permissions — the
same defect class as the report path, more sensitive and longer-lived, but it
predates this PR. Filed as logancyang/obsidian-copilot-preview#250.

Actual Effect

CleanShot 2026-08-06 at 01 26 06

@Emt-lin

Emt-lin commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 823f54d11b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/settings/v2/components/AdvancedSettings.tsx
@Emt-lin
Emt-lin force-pushed the settings-v4-agents-basic branch from 6d61a3f to 8a80d01 Compare August 6, 2026 23:59
@Emt-lin
Emt-lin force-pushed the issue-report-upload branch from 823f54d to edd2f01 Compare August 6, 2026 23:59
@Emt-lin

Emt-lin commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Already looking forward to the next diff.

Reviewed commit: edd2f01920

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@Emt-lin
Emt-lin force-pushed the settings-v4-agents-basic branch from 8a80d01 to 359cbf7 Compare August 7, 2026 00:21
@Emt-lin
Emt-lin force-pushed the issue-report-upload branch from edd2f01 to fb0800e Compare August 7, 2026 00:21
@Emt-lin

Emt-lin commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🚀

Reviewed commit: fb0800ee6e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@Emt-lin
Emt-lin force-pushed the settings-v4-agents-basic branch from 359cbf7 to 4b042d7 Compare August 7, 2026 00:41
@Emt-lin
Emt-lin force-pushed the issue-report-upload branch from fb0800e to 20da64e Compare August 7, 2026 00:41
@Emt-lin

Emt-lin commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 20da64e94e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agentMode/ui/ReportIssueFlow.tsx
@Emt-lin
Emt-lin force-pushed the settings-v4-agents-basic branch from 4b042d7 to b1cfce1 Compare August 7, 2026 00:57
@Emt-lin
Emt-lin force-pushed the issue-report-upload branch from 20da64e to a5f1f26 Compare August 7, 2026 00:58
@Emt-lin

Emt-lin commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a5f1f26fdf

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/utils/issueReport.ts Outdated
@Emt-lin
Emt-lin force-pushed the settings-v4-agents-basic branch from b1cfce1 to 16cdb87 Compare August 7, 2026 01:16
@Emt-lin
Emt-lin force-pushed the issue-report-upload branch from a5f1f26 to c5e27b0 Compare August 7, 2026 01:17
@Emt-lin

Emt-lin commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Already looking forward to the next diff.

Reviewed commit: c5e27b0ee4

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@Emt-lin
Emt-lin force-pushed the issue-report-upload branch 2 times, most recently from 7558427 to f606964 Compare August 7, 2026 02:35
@Emt-lin

Emt-lin commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f606964ef2

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/utils/issueReport.ts Outdated
@Emt-lin
Emt-lin force-pushed the issue-report-upload branch from 08f156d to f73aaa0 Compare August 7, 2026 03:10
@Emt-lin

Emt-lin commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f73aaa0218

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/utils/issueReport.ts Outdated
@Emt-lin
Emt-lin force-pushed the issue-report-upload branch from a4cc9fb to 9fc8a01 Compare August 7, 2026 11:36
@Emt-lin
Emt-lin force-pushed the settings-v4-agents-basic branch from 9290cf4 to cb3229f Compare August 7, 2026 16:09
Base automatically changed from settings-v4-agents-basic to v4-preview August 7, 2026 20:41
Emt-lin added 5 commits August 8, 2026 12:48
- Replace drag-and-drop with upload flow (mock uploader)
- Add 3-page ReportIssueFlow (details → review → done)
- Build linked issue URL with non-truncatable prefix
- Add ReportUploader contract + mockReportUploader
- Expand issueReport/redactLog/logFileManager utilities
- Wire uploader into AdvancedSettings
- Update docs: drag → upload
- Add REPORT_UPLOAD_FLOW.md design doc
The review step invites the user to edit the staging folder before
rebuilding, so a file that has since been deleted is an ordinary edit
rather than a failure. It comes back demoted in the returned outcomes
and the rest of the report still packs, instead of throwing and
stranding the user with nothing to send — the rebuild deletes the old
zip first.

Deletion is not the only way a read can fail, though. A file that is
still there and merely cannot be read — locked by another program, or
its permissions changed underneath us — must not be reported as
"Removed from the report folder": that blames the user for an edit they
never made, while the report goes out a source short on the strength of
it. Only a missing file is a removal; anything else is a failure
carrying the real error, which is the split `assembleReportBundle`
already makes for the same class of trouble.

Reuses the existing `isMissingFileError`, which recognises both a Node
`ENOENT` code and the message-only shapes other adapters throw.
`report.md` remains the one file whose loss stops the rebuild, because
without it there is no issue to file.
…hment

Deleting an attachment from the staging folder and rebuilding leaves
`report.md` still listing it, so the public issue can claim an
attachment the zip omits.

The obvious fix — regenerate `report.md` from the final manifest — is
worse than the problem it solves. The flow explicitly invites the user
to edit the staging folder, and `report.md` is one of the files they may
have edited, including to redact their own prose; regenerating it would
discard that silently. What goes stale is a filename in a list, never
file content, so a user who deleted a screenshot to keep it private
still gets that.

The note records the tradeoff and points at
logancyang/obsidian-copilot-preview#279, which tracks the two ways out:
detecting the discrepancy and letting the user choose, or dropping the
attachment list from `report.md` altogether.
`ReportUploadResult.reportId` had no reader anywhere in the plugin: the
type declared it and the mock produced it, and nothing else touched it.
Its stated purpose was revocation and support correlation, neither of
which exists yet.

The cost of keeping it is not the field but the promise. The upload
endpoint has not been built, so this type is the contract it will be
written against, and a required field obliges it to return something no
caller wants. Revocation, when it lands, can add both the id and the
call that consumes it.
The failure matrix said the object was orphaned and left to server-side
expiry. That was true of an earlier draft; the flow now opens the
prefilled issue whether or not the dialog survived, because the upload
cannot be aborted and withholding the link would leave an upload the
user can neither see nor use. The matrix is where a reader goes to look
up exactly this boundary, so it was the one place the code and its own
description disagreed.

Covers the two paths that were asserted nowhere: a Retry that really
uploads a second time (proving the single-flight lock is released on
failure), and a failure arriving after the dialog is gone, which must
open nothing and write no state.
@Emt-lin
Emt-lin force-pushed the issue-report-upload branch from 2732e1d to 931f37e Compare August 8, 2026 04:49
Base automatically changed from v4-preview to master August 8, 2026 05:02
@Emt-lin
Emt-lin changed the base branch from master to v4-preview August 8, 2026 05:30
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.

1 participant