The problem
What problem does In-app bug reports solve?
Users find bugs; engineers fix them in GitHub. Between the two sits an e-mail, a support ticket or a screenshot in a chat, and the report loses detail at every hop. The user never hears whether it was fixed.
A "report a bug" button that opens a GitHub issue looks easy. It goes wrong in quiet ways. A retry opens the same issue twice. An outage at GitHub loses the report. An internal comment from the team shows up in front of a customer. A screenshot sits at a public URL. The app says "open" long after the bug was closed.
This skill gives a coding agent the bridge the way we build it: the report is saved in the app first and reaches GitHub through an outbox, so an outage delays it and loses nothing. GitHub's own state becomes the report's status. The team answers with /reply comments, the only ones a user sees. Attachments stay behind sign-in, operators see every tenant in one view, and an in-app assistant can draft a report that the user sends.
The module
What does the skill build?
An agent with this skill builds one module for a Next.js App Router app, on your stack. For In-app bug reports that module consists of:
- 1. Reports with markdown and attachments saved under tenant policies first and delivered through an outbox with marker de-duplication
- 2. The issue's state mirrored back as the report's status
- 3. /reply comments as the only team replies a member sees
- 4. Attachments that stay private behind sign-in
- 5. An operator view across tenants with bridge health
- 6. An assistant tool that drafts a report the member sends
What it needs from you
- Next.js App Router with Server Actions and
after(), on a Node runtime. - Postgres with Supabase for the reference store: RLS, Storage and the two clients. Another stack ports the store on the rules in
references/adaptation.md. - A GitHub App you control, installed on one private repository, and a host the proxy serves without resolving a tenant for the operators and the webhook.
- A scheduler that calls a route every five minutes with a bearer: Vercel Cron, or any other.
zod,@supabase/supabase-js,@supabase/ssr,server-only,vitestfor the suites, andai7 for the assistant tool, installed with the app's package manager.
Provenance
Where do the rules come from?
This skill was written by the engineer who has shipped this module. The earlier implementation it was audited against was the bug report bridge of a multi-tenant console on Next.js 16, Vercel and Supabase, with an assistant on the AI SDK. The templates hold the properties such a bridge has to hold: one issue per report however often a job retries, no team comment shown to a tenant without an explicit /reply, no attachment reachable without a sign-in, workers and the webhook reachable on a multi-tenant proxy, and an assistant that drafts but cannot send. The suites in `references/testing.md` state each one; `references/provenance.md` has the record.The engineering ledger behind the templates, for the person editing this skill. It keeps three things apart: what the audit of the earlier implementation changed and how the templates verify it, what was kept deliberately and why it is safe, and what was designed here and has never run in production.
The ledger separates what the audit changed, what was kept on purpose, and what has not run in production yet.
Fixed in the templates
- Vercel Cron could not reach any worker The proxy resolved the tenant from the host before anything else. Cron calls the deployment's own
*.vercel.apphost, which no tenant owns, so every cron route in the app, not only this module's, answered 404 from the proxy. Development did not show it: the local seed mapped a*.vercel.appwildcard to a test tenant. Shipped:CRON_PATHS, the bypass placed first in the proxy, and a test that the list equalsvercel.json, see outbox.md. - The webhook host answered "Domain not configured" The App's webhook pointed at the operator host, which the proxy did not yet treat as a host without a tenant. Every delivery answered 404 before the route ran. Shipped: a critical fact, an
OPERATOR_ORIGINthe env reader requires, and acurlprobe in the go-live list that tells a proxy 404 from the route's 401, see webhook.md, operations.md. - The text written on GitHub was in the tenants' language Table labels, link text and comment signatures were hard-coded in the language of the console. The repository is read by engineers, in the repository's language. Shipped:
TRACKER_STRINGS, English by default, separate from the members' copy, see adaptation.md. - Ignored deliveries left no trace An event from the wrong installation or repository, or for an issue no report owned, answered 200 with the reason in the body and was otherwise invisible. A misconfigured App looked exactly like a quiet one. Shipped: the reason logged, and written to
bug_report_bridgefor the health view, see webhook.md. - Sign-in from an attachment link lost the file An anonymous request to an attachment was redirected to sign-in without a return path. Operators open these links from GitHub, usually before they have a session. Shipped:
loginUrl(returnTo)in the host seam, called with the attachment's path, see attachments.md.
Show 3 moreShow fewer
- The lists stopped at a fixed cap The member list read the newest 200 reports and the operator list the newest 300, with no sign that more existed. Shipped: keyset pagination on
(last_activity_at, id)with a cursor in the URL, see actions.md. - The worker logic could not be tested Which deliveries counted, and when a failed job retried, were decided inside functions that also read and wrote the database. Shipped:
planDeliveryanddecideRetryas pure functions with their own suites, see webhook.md, outbox.md, testing.md. - The issue job read the host's members table The worker looked up the reporter's role in a host table at send time. Shipped:
reporter_rolesnapshotted on the report at insert, next toreporter_name, so the module reads no host table at all, see data-model.md.
- Vercel Cron could not reach any worker The proxy resolved the tenant from the host before anything else. Cron calls the deployment's own
Kept deliberately
- The webhook is processed inline It looks slower than answering first and working in
after(). GitHub does not redeliver a failed delivery by itself, so work after the answer turns every failure into a silent 200. Inline, a failure is a 500 in the App's log and a Redeliver button; the ledger makes that safe. - Visibility is opt-in, through /reply from the team It costs the team a prefix on every answer. Every alternative that mirrors by default leaks the team's own conversation the first time someone forgets a marker.
- No status writer but the tracker Operators get no close button either. Two writers of one field is how an app and a tracker disagree about whether a bug is fixed.
- Markers are found by listing, not by search Listing the label's issues with
sincelooks heavier than one search query. GitHub's search does not index HTML comments, so a search for the marker returns nothing and the retry opens a duplicate. - No role check for reporting The person who hits a bug is rarely the person with an admin role. Every member may report and comment, and the RLS admits any member; a per-member revoke would hide a button the database still serves.
- The webhook is processed inline It looks slower than answering first and working in
Added
- A comment waiting for its issue gets its attempt back In the earlier implementation a comment job waiting for its issue spent attempts like a failing one, so during an outage it could be parked while its issue was not.
decideRetryrefunds the attempt while the issue job is pending. Designed here; it has never run in production. See outbox.md. - The bridge health view Last delivery, last ignored delivery and why, last worker run, pending jobs and failed reports, derived from timestamps the bridge writes. Designed here; the earlier implementation showed only per-report sync states. See operations.md.
- The test config skips the installed skill
vitest.config.tsshipped in 0.1.0 withinclude: ["**/*.test.ts"]and onlynode_modulesexcluded, so in an app with the skill installed in the project, under.agents/skills/or.claude/skills/, vitest also collected the skill's ownassets/tests/copies, whose relative imports resolve nowhere, andnpm testfailed. Found by the 0.1.0 agent evals, where one agent patched the config to get its run green; reproduced against the template and fixed with.*/**inexclude. Designed here. See testing.md. - Marker lookups read every page
findIssueByMarkerandfindCommentByMarkershipped through 0.1.1 reading only the first page of 100. A retry after more than a hundred newer labelled issues, or on an issue with more than a hundred comments since the job began, missed its own marker and opened a duplicate. Found by the 0.1.1 agent evals, where one agent paged the lookups; reproduced with a fakefetchthat puts the marker on page 2, and fixed by paging until a short page, with a test inserver/github/app.test.ts. Designed here. See github-app.md. - A failed upload never ends as "uploading" The upload picker shipped through 0.1.2 ran each upload in an async block with no
catch. When the action was unreachable, orcreateBrowserClientthrew because theNEXT_PUBLIC_Supabase values were not in the bundle, the file stayed "uploading" and the form, which waits for every upload, could never send. Found by the 0.1.2 agent evals, where one agent added thecatch; reproduced by callingcreateBrowserClientwithout its config, which throws. The block now ends in an error state on any throw. No suite covers it: the suites run without a React environment. See attachments.md.
- A comment waiting for its issue gets its attempt back In the earlier implementation a comment job waiting for its issue spent attempts like a failing one, so during an outage it could be parked while its issue was not.
Non-negotiables
What are the 5 rules the module never breaks?
Every module built from this skill holds these, whoever builds it. The same list is in the skill's README and SKILL.md, so the agent reads it before it writes a line.
Never let the app write a status.
GitHub's state is the only writer, through the webhook and the reconcile pass. Members have no UPDATE policy and no privilege, and nobody has a close button; the RLS checks in
references/data-model.mdshow an update refused.Never show a tracker comment to a tenant unless it opts in.
Visible only with
/replyon the first line from an owner, member or collaborator of the repository.parseReplyCommandandplanDeliverycarry the rule and their suites hold it.Never call the tracker inside the request that saves the report.
Save, enqueue, return; the outbox sends and de-duplicates by marker on every retry, so a report is never refused or doubled by GitHub's state.
Never publish an attachment.
Private bucket, a 404 for everyone but an operator or the report's tenant, a 60-second signed URL, no SVG or HTML. The policies confine reservations to the tenant's folder.
Never let the assistant send.
The draft tool reads nothing and writes nothing; the member's click on the card runs the same Server Action as the form.
Fit
When should you use it, and when not?
Use it for
- A product wants its users to report bugs from inside the app, into the repository the team uses.
- The team wants to answer and close reports in GitHub and have the app reflect it.
- An assistant in the app should help write a report without being able to send one.
Not for
- Capturing crashes and exceptions automaticallyInsteadAn error monitoring service. This module files what a person writes
- A help center with articles and searchInsteadThe
help-center-markdownskill; link its pages to the report form - Customer support conversations and SLAsInsteadA support inbox. Reports here go to engineers, not to agents
- An assistant in Slack with approvalsInsteadThe
slack-ai-botskill - Mirroring every issue of a repositoryInsteadA GitHub sync tool. This module bridges the issues it created, by label and marker
Build it yourself
How do I install it?
One command. The skills.sh CLI installs the skill into every skills-compatible agent it finds.
$ npx skills add timerise-ai/in-app-bug-reportsClaude Code
Invoke with /in-app-bug-reports
Codex CLI
Invoke with $in-app-bug-reports
Gemini CLI
Invoke with /skills
Name the agents instead with -a, for example npx skills add timerise-ai/in-app-bug-reports -a claude-code -a codex. Or clone the repository into your agent's skills folder. Nothing in it is agent-specific.
What is inside the repository (21 entries)
SKILL.mdEntry point: architecture diagram, critical facts, hard rules, quick start, and the reference directoryREADME.mdThis fileCHANGELOG.mdOne section per release, newest firstCLAUDE.mdThe editing conventions, for an agent editing this repositoryLICENSEMITreferences/adaptation.mdThe seam contract:BugReportsHost, the client UI module, the two SQL functions, the strings, the rename table, other backendsreferences/data-model.mdThe types, the migration with its policies, numbering trigger, outbox, ledger, bridge row and bucket, and the checks it was put throughreferences/github-app.mdRegistering the App, the environment and.env.example, the JWT and token client, theIssueTrackerseam, what an issue saysreferences/outbox.mdThe retry policy, the two jobs, the worker and reconcile, the health record, the cron route and the proxy bypassreferences/webhook.mdSignature, the delivery plan and its table, the visibility rule, echoes, applying it, the route and its hostreferences/attachments.mdReserve, upload, claim, read and sweep; the sign-in route; the browser pickerreferences/actions.mdThe read models with keyset pages, the member actions, the operator actionsreferences/member-ui.mdEntry points, the markdown field, the form, the list and the threadreferences/notifications.mdWho is told what, one notice per report, sending and marking readreferences/assistant-tool.mdThe draft-tool pattern, the tool, registering it, the confirm cardreferences/operations.mdThe operator view and its health line, the go-live list, the failure table, credentials, privacyreferences/testing.mdThe nine suites, 50 tests, how to wire them, what they do not cover, an integration test worth addingreferences/provenance.mdThe engineering ledger: what the audit changed and how the templates verify it, what was kept on purpose, and what is new in the skillassets/tests/The nine suites as files, at the paths on their first linesevals/The prompts an operator types after installing (prompts.md) and one file per agent eval: the skill installed into an empty Next.js app, one prompt, no help, then type-checked, built and tested.github/workflows/agent-eval.ymlThe caller of the index's reusable eval workflow, run on every published release and on a maintainer's dispatch
Recent releases
- v0.1.4October 7, 2026
Fix release, from scoring the prompt-1 agent eval runs against 0.1.3.
- v0.1.3October 7, 2026
Fix release, from scoring the prompt-1 agent eval runs against 0.1.2.
- v0.1.2October 7, 2026
Fix release, from scoring the prompt-1 agent eval runs against 0.1.1.
After installing
What do I tell my agent?
Say what you need in your own words; the skill supplies the how. These are starting points, and the ones we tested say how it went.
Add a 'report a bug' button to this app. People should be able to write what went wrong, attach screenshots, and see the team's replies and whether it was fixed. The team works in GitHub Issues.
Our in-app assistant should be able to help a user write up a bug report, but the user has to be the one who sends it.
PostgresBug reports from the app reach GitHub, but closing the issue never updates the report in the app. Find out why.
Tested
How does it do in each agent?
We install the skill into an empty Next.js app, give the agent one of the prompts above and no further help, then type-check, build and run the tests it left behind. Nothing is fixed by hand before the checks, and a failing run is published like a passing one. The procedure and every result are public, and the first prompt runs again before each release.
- Built, checks pass
Codex CLIcodex-cli 0.161.0
gpt-6.1-sol
Add a 'report a bug' button to this app. People should be able to write what went wrong, attach screenshots, and see the team's replies and whether it was fixed. The team works in GitHub Issues.
Typecheck: passBuild: passTests: pass- Time
- 2 min
- Changed
- 63 files, +4,924 lines
- Stack
- Postgres
- Skill
- v0.1.4
- Run
- Oct 7, 2026
What we saw
Rubric 8/8, on gpt-6.1-sol with reasoning effort none. It copied every block by script, wrote
proxy.tsfrom the outbox.md block with the webhook besideisCronPathas documented, installed the dependencies, ran the suites unmodified under vitest at 50, tracked the skill's.env.example, left host identity null with a no-opnotifyMembers, and put all three handover points in the final message. Its first clean run. Scored from the transcript. - Built, checks pass
Gemini CLI0.63.0
gemini-3.8-flash
Add a 'report a bug' button to this app. People should be able to write what went wrong, attach screenshots, and see the team's replies and whether it was fixed. The team works in GitHub Issues.
Typecheck: passBuild: passTests: pass- Time
- 15 min
- Changed
- 61 files, +5,168 lines
- Stack
- Postgres
- Skill
- v0.1.4
- Run
- Oct 7, 2026
What we saw
Rubric 8/8. Dependencies installed, the nine suites under vitest at 50,
.env.examplewritten, the bypass inproxy.ts, host identity left null, and the three handover points in the final message. It read "the first six variables in github-app.md" against the.env.exampleblock, whose first three names are Supabase's, and listed nine; the bridge's off state and the waiting reports are still stated correctly. Scored from the summary. - Built, checks pass
Claude Code2.1.292
claude-opus-5-5
Add a 'report a bug' button to this app. People should be able to write what went wrong, attach screenshots, and see the team's replies and whether it was fixed. The team works in GitHub Issues.
Typecheck: passBuild: passTests: pass- Time
- 1 min
- Changed
- 58 files, +4,767 lines
- Stack
- Postgres
- Skill
- v0.1.4
- Run
- Oct 7, 2026
What we saw
Rubric 8/8. Dependencies installed (
aileft out with the assistant tool, which the app has no chat for), the nine suites unmodified under vitest at 50, all eleven names in a tracked.env.example, a newproxy.tswith the bypass and the documented webhook addition, host identity left null, and the three handover points in the final message, including the static 404 prerender. Scored from the summary.
Build it with Timerise
How long does it take, and what does it cost?
We quote this module per project. The price depends on what it has to connect to. The path to a number is short and free:
Step 1
Brief
Tell us what the module must connect to. Takes minutes, in a chat.
Step 2
Prototype in 48 hours
A clickable prototype of your system and a quote, at no cost.
Step 3
Build and handoff
One project price. Source code, documentation and IP are yours.
Two ways to get In-app bug reports
Build it yourself
Install the skill. Your own agent builds the module.
- MIT licensed, no strings
- Runs in Claude Code, Codex CLI and Gemini CLI
- The same rules our engineers build by
$ npx skills add timerise-ai/in-app-bug-reportsBuild it with Timerise
Send a brief. We build In-app bug reports into a system you own.
- Clickable prototype and a quote within 48 hours, free
- One project price, no subscription, no commission
- Source code, documentation and IP handed over
Generated from the skill's own files at commit c13ea8e. Every rule above links to where the repository says it. All skills