Skip to content
Docs

Bug reports

Silah Desk 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 Desk or by the routine.

  • Follow it to the release. Silah Desk follows the pull request to its merge, and then to the release that contains it. When the release ships, Silah Desk tells the reporter it is fixed, in the version it shipped in.

  • Keep the reporter told. Each step the team takes (the report confirmed, an issue opened, a pull request, the merge, the release) tells the reporter in their own language. See Updates to the reporter.

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 Desk 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 Desk

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 Desk 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://silahdesk.com/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 Desk 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 Desk 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 Desk 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 Desk 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": "…",
  "role": "accountant", "permissions": ["Campuses: Main", "Can collect fees, …"] }

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.

role and permissions (up to 30 lines of up to 300 characters) say what the person may do in the product, in its own words. The inbox shows them beside Verified with the install's domain, and the AI is told them so it answers at the level of what the person can actually do. They are a description, never a grant: Silah Desk gets no access to the product because of them, and each new sign-in replaces them.

The SDK: report.js#

One script tag gives any web page a Report a problem button and dialog:

html
<script src="https://silahdesk.com/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 Desk'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 Desk's GitHub App on the repositories it chooses. The person installing must be able to reach the installation; Silah Desk 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 Desk 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 tells the reporter.

  • The report's GitHub panel reads the issue and the pull request from GitHub when it is opened (GET /workspaces/{id}/reports/{cid}/github): title, state (draft, open, merged, closed), who merged it, when and into which branch, the merge commit, whether an open one can be merged, and the size of the change, beside the fix routine's status and every GitHub event the report has seen. Where GitHub disagrees with what the report recorded — a webhook GitHub never delivered — the report is brought up to date and the event is recorded as caught up on refresh.

Silah Desk's own GitHub App is created once per Silah Desk 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 Desk 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 Desk connector (below). A short prompt is enough, because Silah Desk 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. Every category is checked automatically except idea, which is a request rather than a fault.

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

Updates to the reporter#

A report's status (open, pending, resolved) is its conversation's. Change it from the report page's Engineering › Status, or from the conversation in the inbox; the product reads the same status.

A report moves through five stages, and the reporter is told when it reaches one it has not reached before:

StageReached when
receivedthe report arrived from an install or a browser (not a report a teammate typed up)
confirmedthe check found a bug, an issue is linked, or a fix is queued or running
fixinga pull request is open for it
fixedthe pull request is merged
releasedfixed_in_version is set, by a matching release or by hand
  • The stage is read from the report as it stands, never from the event that moved it, so a webhook, the GitHub panel's catch-up, a link pasted by hand and the MCP tools all reach the same answer. bug_report_updates holds one row per (report, stage), claimed before anything is sent, so a stage is told once however many paths reach it. A later stage covers the earlier ones: a report that jumps to released says only that.

  • The language is the reporter's: Bengali if they wrote in Bengali, English if in English, and where the words decide nothing, the ui_language / locale the product sent in the context.

  • The words are fixed sentences (products.UpdateMessage). They say what has been done and never when the rest will be: no update promises a date.

  • The message is a bot message of kind report_update. The product sees it as a team message, and it does not make the ticket wait on the customer. The history records each as reporter_updated.

  • Products › Routines › Keep the reporter told switches it off per product. Off, stages are still recorded, so turning it back on does not send old news. A reply the check routine drafts for a question or a non-bug stays a draft a person sends.

The Silah Desk connector (MCP)#

Silah Desk is a remote MCP server at https://silahdesk.com/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.