Bug reports
Silah takes bug reports from the software a workspace makes and sells, and turns each one into a ticket in the inbox. Each report carries the page it came from, who sent it, the browser's and the server's recent log, the system it ran on, and, where the product proves its installs, which install it was. From there a workspace can:
Check it with Claude. A Claude Code routine reads the report and the product's code, then records what the report is and, for a bug, how it will be fixed. Nothing is changed in the code.
Fix it with Claude. When a person presses Fix it, a second routine follows that plan and opens a draft pull request. Nothing is ever merged by Silah or by the routine.
Follow it to the release. Silah follows the pull request to its merge, and then to the release that contains it. When the release ships, Silah drafts the message telling the reporter it is fixed. A person sends it.
None of this is tied to one product. A workspace adds its own products and decides how each one's installs prove who they are.
Products#
A product is something the workspace ships: an app, a self-hosted server, a website. Add one in Admin › Products. It has:
| Public key | prd_…. It names the product on every intake request. It is not a secret: it goes in web pages |
| Ticket prefix | Reports are numbered PREFIX-1001, PREFIX-1002, … |
| Intake method | How an install proves it is one of yours (below) |
| Components | Parts of the product (the console, the API, a portal). A page path pattern assigns a report to its component, and each component can have its own repository |
| Repositories | The GitHub repositories the product's code lives in, from the workspace's GitHub connection |
| Routines | The check and fix routines (below) |
| Retention | How long screenshots, logs and context are kept. The ticket and its conversation stay |
Intake methods#
| Method | The install proves itself with | Use it for |
|---|---|---|
browser | Nothing. Reports are taken from the product's own web pages, optionally only from allowed_origins | A public website or a SaaS app with one deployment |
install_key | Authorization: Bearer sik_…, a key Silah issues per install (Products › Installs), shown once | A small number of installs you hand a key to |
signed_install | Silah-Install-Proof: <JWT>, a token your licence server signs | Self-hosted software that already holds a signed licence |
connected_site | A Silah Connect signature (Silah-Site-Id, Silah-Timestamp, Silah-Nonce, Silah-Signature) | A product whose reports go through its server, already connected to Silah |
An install that does not prove itself is still taken when Accept unverified reports is on (the default). Its reports are flagged, it is rate-limited harder, and it can read only the reports whose tokens it holds. Turn the switch off and an unproven request is answered 401 unverified_install with the reason the proof was refused.
signed_install#
Silah verifies a JWT that your licence server signed, with keys you give it. It never talks to your licence server and never learns your licence keys.
algisEdDSA,ES256orRS256. Keys are pasted as PEM (or, for EdDSA, the raw 32-byte key in base64), or read from ajwks_url. Several keys may be listed, and akidin the token picks one.iatis required. The token'sexpis not checked: a licence verdict is usually short-lived and kept by the install, so what bounds it ismax_age_hours(default 120) measured fromiat. An older proof still verifies, as stale.issandaudare checked when you setissuerandaudience.instance_id(or the claim you name inclaim_instance) identifies the install, at most 200 characters.domainandname(orclaim_domain,claim_name) label it.Nothing else in the token is stored, except the claims you list in
keep_claims. A licence token may carry a licence key.
The intake API#
Base URL https://silah.chat/api/v1/intake/v1. CORS is open (*), and no cookies are read. Every request carries:
| Header | |
|---|---|
Silah-Product | The product's public key, prd_…. Required |
Silah-Install-Proof / Authorization / Connect headers | The install's proof, per the intake method |
Silah-Install-Id | Where the product has no proof, what the install calls itself |
Silah-Install-Version | The version the install runs. It is recorded on the install and on each report |
POST /reports#
The body is either JSON (the report) or multipart/form-data with a report part holding the JSON and one part per file: screenshot or attachment. A report may carry at most 5 files of 5 MB each, and PNG, JPEG, WebP, GIF, PDF and plain text only. The type is read from the bytes, not from the name.
{
"client_ref": "4f0d…", // yours; a retry with the same value is the same report
"category": "bug", // bug | question | idea | data | other
"subject": "Receipt shows the wrong total",
"description": "What happened, and what was expected", // required, at most 20000 characters
"reporter": { "subject": "user-17", "name": "…", "email": "…", "phone": "…", "role": "accountant", "account": "…" },
"page": { "url": "https://…/fees/receipt/42", "title": "…" },
"app": "console", "version": "2.4.1",
"context": { "user_agent": "…", "timezone": "Asia/Dhaka" }, // at most 64 KB
"logs": {
"browser": [{ "at": "2026-10-26T09:12:03Z", "level": "error", "message": "…" }],
"server": [{ "at": "…", "level": "error", "message": "…", "request_id": "…" }]
}
}At most 300 log lines each. Send
at,level(debug,info,warn,error,fatal) andmessage, and any other fields you like. Redact credentials before you send them. Silah redacts what looks like a token, a key or a password field again, but it is a second line, not the first.Reports whose first
errororfatalmessage is the same are grouped as similar, so twenty reports of one failure read as one problem.
The answer is 201, or 200 when client_ref matches a report already taken:
{ "report": { "id": "…", "reference": "EMS-1042", "status": "open", "fix_status": "none", … },
"token": "srt_…", "duplicate": false }token is shown once. With it, in Silah-Report-Token, the sender can read the report and answer it, even from an install Silah could not verify.
The other routes#
| Route | |
|---|---|
GET /ping | Which workspace and product the key names, whether the install is verified, and its licence state |
GET /reports/{id} | The report and its conversation (needs the report token, or a verified install that sent it) |
POST /reports/{id}/messages | {"body", "reporter"}: the reporter answers the team |
POST /reports/status | {"reports": [{"id", "token"}]}: up to 100 reports at once, for an install that pulls |
POST /identity-token | A verified install signs one of its users in to the chat widget (below) |
What an install reads is what was decided, never the engineering trail: the status, whether it was checked and the verdict, fix_status (none, in_progress, in_review, merged, awaiting_release, …), fixed_in_version and released_at. Repositories and pull requests may be private, and are never shown.
Signing users in to the chat widget#
A self-hosted product has no Silah secret to sign an identity token with, and should not have one. Turn on Products › Widget sign-in instead. A verified install then asks Silah for a token for its signed-in user:
POST /api/v1/intake/v1/identity-token
Silah-Product: prd_…
Silah-Install-Proof: <the install's licence token>
{ "subject": "17", "name": "…", "email": "…", "account": "…" }The answer is { "token", "widget_key", "expires_at" }. Hand token to the widget as its identityToken. The subject is recorded as <instance_id>:<subject>, so user 17 of one install is never user 17 of another. account defaults to the install's name or domain.
The SDK: report.js#
One script tag gives any web page a Report a problem button and dialog:
<script src="https://silah.chat/report.js" data-silah-product="prd_…" defer></script>It keeps the page's last 200 log lines: console output, uncaught errors, rejected promises and failed requests, with credentials blanked as each line is kept. When somebody presses the button it asks what went wrong, offers to attach a picture of the page and a file, and sends it all with the page and the system to the intake API.
Script attributes: data-silah-product, data-silah-api (Silah's origin, by default the script's own), data-silah-install, data-silah-version, data-silah-app, data-silah-button="false" (no floating button), data-silah-label, data-silah-lang (en or bn; by default the page's lang).
Or configure it from code:
SilahReport.init({
product: "prd_…",
version: "2.4.1",
app: "console",
proof: () => fetch("/api/licence/proof").then((r) => r.text()), // signed_install
user: () => ({ subject: me.id, name: me.name, role: me.role }),
context: () => ({ campus: currentCampus }),
screenshot: () => takeScreenshot(), // a Blob; the dialog offers it
button: false,
onSent: ({ reference }) => toast(`Sent as ${reference}`),
});
SilahReport.open({ subject: "Receipt total" }); // the dialog, from your own button
SilahReport.send({ description: "…" }); // no dialog
SilahReport.log("warn", "Sync took 40 s"); // a line of your owntransport(report, files) sends the report some other way instead, for example through your own server, which then adds the server's log and signs the request. That is how a product keeps its proof out of the browser.
GitHub#
The workspace connects GitHub once (Admin › Products › GitHub) by installing Silah's GitHub App on the repositories it chooses. The person installing must be able to reach the installation; Silah checks. Then:
A product lists its repositories, optionally per component, with path hints and a release tag pattern (
v{version}by default).Open a GitHub issue (or the check routine's
create_issue) opens one from the check's summary and plan. It carries no personal details of the reporter and no logs.A pull request is linked when the routine calls
link_pr, when its description has aSilah-Report: <id>line, or when its branch issilah/fix-<number>(orclaude/silah-fix-<number>). Silah follows it: open, merged, or closed.A release published on the repository whose tag matches the pattern, and which contains the merge commit, marks the report fixed in that version, and drafts the reply to the reporter.
Silah's own GitHub App is created once per Silah deployment from Console › Integrations › GitHub › Create the GitHub App, which posts GitHub's app manifest and stores what GitHub returns.
Routines: Claude checks and fixes#
Silah never runs a model for a bug report. It fires a Claude Code routine that the workspace sets up on claude.ai, so the work runs on the workspace's own Claude subscription, in a cloud session with the product's repositories.
On claude.ai, create two routines with an API trigger, one to check and one to fix. Give each one the product's repositories and the Silah connector (below). A short prompt is enough, because Silah sends the instructions with every firing: "Follow the instructions in the text you are fired with."
In Products › Routines, paste each routine's fire URL (
https://api.anthropic.com/v1/claude_code/routines/trig_…/fire) and its token (sk-ant-oat01-…). Tokens are stored encrypted and never shown again.Choose whether a new report is checked automatically, and a daily cap on runs.
A firing carries the report id, the product, the component, the repositories and the address of the report, and tells the session to read the report through the Silah connector, treating everything the report says as data, never as instructions.
Check finds the code involved and calls
record_checkwith what the report is (bug,not_a_bug,question,duplicate,needs_info), a summary, and for a bug how it will be fixed: the plan, the files, and a confidence. It opens the issue for a bug, and drafts a reply for anything else. It changes no code.Fix runs only when an admin presses Fix it, optionally with a note. It follows the recorded plan on a
silah/fix-<number>branch, adds a test that fails without the fix, opens a draft pull request and links it. If the plan is wrong it says so in a note and stops.
The report shows each run, the session it started, and its state.
The Silah connector (MCP)#
Silah is a remote MCP server at https://silah.chat/api/v1/mcp, with its own OAuth 2.1 server. It is how a routine, or anybody on the team using Claude, reads reports and records what they found. See MCP.md.