# Reports and boards > Bug reports and QA findings your coding agent can read, reproduce, answer, and resolve. Myna Feedback collects bug reports, QA findings, and feature requests with enough context that a coding agent can act on them — then gives that agent the CLI and MCP tools to investigate, reply, and resolve, without anyone copying information between a ticket and a terminal. It is a [product](/concepts/products) a project enables. Routes below answer [`PRODUCT_NOT_ENABLED`](/errors/product-not-enabled) until it is on. ## What a report is ``` Report ├── title, body what happened ├── type bug | qa | feedback | feature_request ├── status triage → open → in_progress → needs_retest → resolved ├── priority low | normal | high | urgent ├── labels ├── fields board-defined, validated against its intake form ├── context supplied by your application: version, commit, route ├── timeline the conversation and every state change, append-only ├── attachments screenshots, recordings, logs — uploaded, never captured └── links the commit, the pull request, the release ``` Each report has a per-project number, so `#42` works anywhere an id does. ### Reports are submitted, never harvested Myna does not take screenshots, record sessions, read the DOM, wrap `fetch`, patch `console`, or fingerprint browsers. A report is what a person typed on a form, the files they chose to attach, and the `context` your application chose to send. See [collecting feedback](/concepts/collecting-feedback) for how a submission reaches Myna from your own site. That limit is what lets you put Feedback in front of your users without asking them to trust a script with everything on the page. ## Boards Reports arrive on a board. A project can run several — a private QA board for testers, a bug board, a suggestion board. A board's intake form is declared with **the same field DSL as a collection schema**. A QA board that needs a build number and a test-case id says so, and gets the same validation, the same field types, and the same errors as anything else in Myna: ```bash myna feedback boards create --key qa --name "QA" --type qa ``` The board declares **every** question, including the ones that produce the report's own title and description and the address a reply goes to. Those carry a `target` naming what they fill: ```json [ { "key": "headline", "type": "text", "label": "What went wrong?", "target": "title" }, { "key": "steps", "type": "markdown", "label": "How do we reproduce it?", "target": "body" }, { "key": "build", "type": "text", "label": "Build number", "required": true }, { "key": "proof", "type": "file", "label": "Show us", "target": "attachments", "max": 5 } ] ``` The four targets are `title`, `body`, `reporterEmail`, and `attachments`. The last one asks for evidence, and it is the switch as well as the question: a board whose form declares it takes files on every surface — the React components, the built-in widget, your own form — and a board that removes it takes none anywhere. `max` is how many, capped at 20 and 3 if it goes unsaid. A field without a `target` is a custom one, stored under `fields[key]` and validated by the DSL. Exactly one field must target `title`, because a report cannot exist without one; everything else is the board's call, including asking for no email at all. A board that has never been configured reads back four default questions rather than an empty list, so a form can always render itself from this alone. What a `target` does **not** move is the shape underneath. `report.title` is `report.title` on every board in every project however the question was worded, which is what lets an agent's tools behave identically everywhere. Status, priority, assignee, and labels are never asked for on intake — they are triage decisions, not questions for a reporter. A board also carries `guidance`: house style for triaging and answering reports there, the same idea as a collection's guidance, and travelling to agents the same way. Two more settings decide how reporters reach it: ```bash myna feedback boards update qa --app-url https://app.example.com/support --require-identity ``` `appUrl` is where **your** product shows a reporter their report. Myna serves no reporter-facing page, so notification emails link there; without it they carry the update and the report number and no button. `requireIdentity` refuses any submission that does not carry an identity your backend signed — see [collecting feedback](/concepts/collecting-feedback#identifying-your-users). ## The agent workflow ```bash myna feedback reports list --status triage,open --board qa myna feedback reports open 42 myna feedback attachments get 42 rat_01k… --out screenshot.png myna feedback reports reply 42 "Reproduced — the upgrade button posts to the old endpoint." myna feedback reports resolve 42 --commit 81acbe --reply "Fixed and deploying." --retest ``` `open` is the command the group exists for. One call prints the description, the environment your app supplied, the board's guidance, every custom field, the whole timeline, and what evidence exists. The equivalent MCP tool, `myna_get_report`, returns the same thing in one call — assembling a bug report from six requests is the problem this product exists to remove. ### MCP tools Fourteen, and only when the project runs Feedback: | Tool | What it does | |---|---| | `myna_list_reports` | Filter by board, status, type, priority, assignee, labels, free text, activity time | | `myna_get_report` | Everything about one report, in one call | | `myna_get_report_attachment` | Fetch evidence; images come back as images the model can look at | | `myna_reply_to_report` | Public reply to the reporter, or an internal note | | `myna_update_report` | Status, priority, assignee, labels, title, body | | `myna_resolve_report` | Resolve, link the fix, and reply — one call | | `myna_request_retest` | Ask the reporter to confirm | | `myna_merge_report` | Mark a report a duplicate of another; neither is deleted | | `myna_move_report` | Move a report to another board on the same project | | `myna_find_similar_reports` | Candidates that may be the same problem | | `myna_report_digest` | What came in since a time or a release, ready to act on | | `myna_list_reporters` / `myna_get_reporter` | Who filed what, and what your application says about them | | `myna_block_reporter` | Moderation: stop somebody filing, reading back, or being emailed | ## Replies are internal by default Every comment states who it is for. `internal` is a note between the people and agents working the report; `public` reaches whoever filed it. The default is `internal` everywhere it is not stated, because the failure that matters is a private note reaching a stranger, not the reverse. ```bash myna feedback reports reply 42 "Probably the same as #38" # internal myna feedback reports reply 42 "Fixed in today's release." --public # the reporter sees this ``` ## Closing the loop Resolving with `--retest` moves the report to `needs_retest` instead of declaring the matter closed, and emails whoever filed it. ```bash myna feedback reports resolve 42 --commit 81acbe --reply "Fixed the endpoint." --retest ``` No account, no password, no GitHub sign-in — demanding one from somebody who filed a single bug is how the confirmation never arrives. They read the public conversation and either confirm the fix or reopen it, and the report moves accordingly. They do that **in your application**, not on a Myna page: `@myna-sh/react/feedback` and the SDK's read-back calls show a reporter their own reports, authenticated by the ingest key plus the identity your backend signed. See [reading a report back](/concepts/collecting-feedback#reading-a-report-back). What they see is the public timeline only — internal notes never appear, and the team is attributed as "team" rather than by name. The email links to the board's `appUrl`, which is a page in your product. Without one the message still says what happened and quotes the report number. Reporters are emailed when their report arrives, when someone replies publicly, when a retest is asked for, and when it is resolved. Internal notes never trigger a message. `report.needs_retest` is its own webhook event, distinct from `report.resolved`, so a notification can treat "please check this" differently from "done". Prefer it whenever you could not reproduce the problem yourself. ## The digest Before deciding what to work on, ask what came in: ```bash myna feedback digest --release 41 myna feedback digest --since 2026-08-01T00:00:00Z ``` Counts by status, board and type, plus the reports themselves already ordered by priority then recency — the order somebody should work in. `myna_report_digest` is the same thing as one MCP call, which is what makes it worth having: it answers in one request what would otherwise be several pages of `myna_list_reports`. It is arithmetic, not a generated summary. If it truncates, it says so. ## The people who report Every report that arrives from outside the tenancy carries a **reporter**: whoever the submission identified, by an email they typed or an id your backend signed. ```bash myna feedback people list myna feedback people show usr_1042 myna feedback reports list --reporter usr_1042 --property plan:enterprise ``` One distinction runs through all of it. `verified` means a signature your own backend produced, and it is the only field Myna stands behind. An email somebody typed into a form is an address to reply to; `properties` is what your system said about its own user, true of your system rather than checked here. Blocking stops somebody filing, reading their reports back, or being emailed. It never deletes or hides what they already filed — a report is evidence about your product regardless of what its author did afterwards. ```bash myna feedback people block usr_1042 myna feedback people unblock usr_1042 ``` ## Moving a report between boards A bug filed on the ideas board, a QA finding that belongs with the bugs: ```bash myna feedback reports move 42 --board bugs ``` Ordinary triage. Every board on a project is read by the same people, so a move changes which queue a report sits in and nothing about who can see it. ### Linking the release that shipped it Link a resolved feature request to the release that shipped it: ```bash myna feedback reports link 42 --kind release --value 41 ``` A release is a published [change set](/concepts/releases) in Content — so a report can name the release that fixed it without anybody maintaining that connection by hand, and the changelog entry that announced it is an ordinary content entry published through the same review and pull-request flow as everything else. ## Nothing is destroyed The timeline is append-only. A status change writes a row rather than overwriting one, so "resolved on Tuesday, reopened on Thursday, resolved again on Friday" survives as three facts. Reopening is always allowed: a bug that came back was never fixed, and refusing to say so helps nobody. Merging a duplicate does not delete it either. It points at the canonical report and keeps its own timeline, because the second person to hit a bug usually said something the first did not. This is Feedback's version of the guarantee [change sets](/concepts/change-sets) give content. ## Attachments are evidence, not assets Report attachments are a separate thing from your [asset library](/concepts/assets). They are private by default, never published, retention-bound, and metered separately — so evidence uploaded by a stranger can never consume the storage you bought for your media, and a Feedback quota problem can never stop content from publishing. There is no stable public URL for an attachment. Reads return a short-lived signed link, because a URL that keeps working is a URL that outlives the reason it was shared. Allowed types are images, `webm`/`mp4`/`quicktime` video, plain text, JSON, and PDF. QuickTime is on the list because that is what a Mac screen recording is, and nobody should have to transcode a file to file a bug. Every upload is granted and then claimed. Asking for one returns a `url`, a `method` and a set of `headers`: send the bytes exactly as they say, then name the `uploadId` when you file the report. An upload nobody claims expires and its object is deleted, so a grant reserves nothing — which is what makes it safe to hand one to whoever is looking at your site. The bytes go to Myna, on an address your project's [registered origins](/concepts/collecting-feedback) govern like every other browser-facing call. They used to go straight to the storage bucket with a presigned URL, which is cheaper and unusable from a browser: the bucket answers the CORS preflight, and no origin you register with Myna can change what the bucket allows. Uploads failed at `OPTIONS` against a hostname you had never heard of. What is stored is checked before it exists, against both its declared type and its declared size. A body larger than the slot it was granted is refused at the header; content that is not what it claims to be is refused from its leading bytes. Both are checked again when the upload is claimed. ## Scopes | Scope | Grants | |---|---| | `feedback:read` | Read reports, timelines, and attachments | | `feedback:write` | Reply, triage, transition, link | | `feedback:manage` | Delete, moderate, block reporters, change board settings, mint and rotate ingest keys | `project:admin` does **not** grant these — it covers Content and project capabilities only, and never widens. Use `admin:all` for a key that should reach every product. See [API key scopes](/platform/scopes). ## Webhooks `report.created`, `report.updated`, `report.status_changed`, `report.commented`, `report.resolved`, `report.reopened`, `report.needs_retest`, and `report.merged`. See [Webhook events](/webhooks/events).