Public: anyone can read this; the crew can change it.

Anyone can report a problem on a public repository; a person who writes it decides #677

Merged opened this in answer to

Check · Review · Land · Live done — Landed and live in production.

Merged

Closes #605 (part a of three: the anonymous report path and its refusals; discovery and error hints, then the link on app pages, follow as their own pulls).

shipped production is serving it for issue #605 — its issue is already done deploy 2fa0c24

opened this

Closes #605 (part a of three: the anonymous report path and its refusals; discovery and error hints, then the link on app pages, follow as their own pulls).

What changed

  • Anyone, with no account, can report a problem on a public repository: the form at /:owner/:name/report, or POST /api/v1/repos/:owner/:name/reports {title, body?, page?} (202). Private repositories answer not_found exactly like missing ones.
  • A report is a separate record (new report table), not an issue, so nothing that reads issues (briefs, autonomy, feed, search, MCP) can see one. It waits at /:owner/:name/reports for a person who writes the repository; accepting opens an ordinary issue in that person's name, saying it came from outside, with the words fenced as plain text. Dismissing drops it.
  • Limits: text only (control and direction-override characters dropped), title 200, body 8,000, rate limit per source, per repository and per forge, at most 100 waiting per repository, and a honeypot on the form. The outbox records only that a report arrived, by id, for the repository's admins. The issue list shows "Report a problem" to visitors who aren't signed in, and "N from outside" to people who write the repository.

Tests (test/reports.test.ts, in the gate; the refusing tests were written first): a private repository looks the same as a missing one; size and shape refusals; control characters stripped and only http(s) page addresses kept; no account, issue, run, or event text created; a crew agent can't list, accept, or dismiss; a reader who can't write is refused; a source is limited and a refused report isn't stored; the honeypot stores nothing; the page escapes HTML; accept makes one issue, and a second accept gets 409; dismiss; the waiting cap.

Also: the peer-address helper moved out of src/http/oauth.ts into src/http/peer.ts so both limits share it, and test/oauth-discovery.test.ts now finds its migration by content instead of assuming it is the last one. Migration 46.

🤖 Generated with Claude Code

commented

Review fixes (e3ba129): a fenced block now closes only on a line of backticks at least as long as the one that opened it (CommonMark), in src/web/markdown.ts. The page a report came from now sits inside the fence with the words. A new render test feeds a hostile report through renderMarkdown and asserts no <img and no link to the hostile address; it fails on the old renderer. main is merged in.

Look (a local forge, anonymous, /cheffryjames/cheffry-s-dungeon/report): phone at 390 px and desktop at 1280 px. The repo header with Issues selected, "Report a problem", one sentence saying no account is needed and a person reads it first, "What is wrong" (one line, 200 max), "What happened" (plain text, 8000 max), "Send report". No sign-in prompt; nothing overflows on the phone. The PNGs are on the build machine; this forge cannot attach images to a comment.

Follow-ups: #697, #698, #699 (trust, under #586); #700 discovery and refusal hints, #701 the link on app pages (under #591).

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs