Skip to content
Docs

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 keyprd_…. It names the product on every intake request. It is not a secret: it goes in web pages
Ticket prefixReports are numbered PREFIX-1001, PREFIX-1002, …
Intake methodHow an install proves it is one of yours (below)
ComponentsParts 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
RepositoriesThe GitHub repositories the product's code lives in, from the workspace's GitHub connection
RoutinesThe check and fix routines (below)
RetentionHow long screenshots, logs and context are kept. The ticket and its conversation stay

Intake methods#

MethodThe install proves itself withUse it for
browserNothing. Reports are taken from the product's own web pages, optionally only from allowed_originsA public website or a SaaS app with one deployment
install_keyAuthorization: Bearer sik_…, a key Silah issues per install (Products › Installs), shown onceA small number of installs you hand a key to
signed_installSilah-Install-Proof: <JWT>, a token your licence server signsSelf-hosted software that already holds a signed licence
connected_siteA 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.

  • alg is EdDSA, ES256 or RS256. Keys are pasted as PEM (or, for EdDSA, the raw 32-byte key in base64), or read from a jwks_url. Several keys may be listed, and a kid in the token picks one.

  • iat is required. The token's exp is not checked: a licence verdict is usually short-lived and kept by the install, so what bounds it is max_age_hours (default 120) measured from iat. An older proof still verifies, as stale.

  • iss and aud are checked when you set issuer and audience.

  • instance_id (or the claim you name in claim_instance) identifies the install, at most 200 characters. domain and name (or claim_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-ProductThe product's public key, prd_…. Required
Silah-Install-Proof / Authorization / Connect headersThe install's proof, per the intake method
Silah-Install-IdWhere the product has no proof, what the install calls itself
Silah-Install-VersionThe 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.

json
{
  "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) and message, 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 error or fatal message 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:

json
{ "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 /pingWhich 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-tokenA 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:

http
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:

html
<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:

js
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 own

transport(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 a Silah-Report: <id> line, or when its branch is silah/fix-<number> (or claude/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.

  1. 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."

  2. 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.

  3. 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_check with 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.