← GoConc Desk / API
Tokens

Drive GoConc Desk from your own code

The page reads pasted Go in the browser for free — one or more files joined into one paste, each introduced by a // file: <path> line, plus go.mod for the Go version — with its line numbers. It lexes strings, runes and comments out first, matches every brace, inventories every function, goroutine (go statements and errgroup Go), loop, select, channel, Lock/Unlock pair, WaitGroup, derived context and HTTP handler, and runs 23 deterministic checks drawn from Go concurrency practice (catalogue ids GC-01..GC-23, plus GC-24 for context): loop variables captured by goroutines before Go 1.22, wg.Add inside the goroutine it counts, WaitGroups that never balance, locks and WaitGroups copied by value, a Lock with no Unlock on every path, RLock released with Unlock, maps and counters written from goroutines with no lock, atomic and plain access mixed, goroutines blocked forever on an abandoned unbuffered channel, a range over a channel nothing closes, a close from goroutines started in a loop, goroutine loops that cannot be stopped, busy-spinning selects, discarded cancel funcs, context.Background() where a context is in scope, goroutines that outlive their request, goroutine errors nobody sees, unbounded fan-out, time.After in a loop, tickers never stopped, time.Sleep as synchronisation and a context stored in a struct. Nothing is compiled or run, and it is not the race detector: every flag is a reading of the text. Its two paid steps are available over HTTP. You send one JSON object with task set to "audit" or "fix", the paste as code and the page’s check as a facts string, and the model replies with one JSON object: an audit that rates the four areas, answers every flag and gives exact before/after edits per finding, or the whole fixed file(s) with a list of every change tied to the page’s flags.

Base URL: https://api.skillsafe.ai/v1/app-api. Every call sends Content-Type: application/json and, once you have a token, Authorization: Bearer <token>. The paths below (/guest, /me, /estimate, /run, /jobs/{job_id}, /run-stream) are relative to it, exactly as sdk.js calls them. A plain-text summary of the app for agents is at /llms.txt. Both lanes run on the gpt-terra model with a 10% publisher markup. A run needs a signed-in (user) token and is metered: the platform places a hold on your credits when the run starts and then charges only what the run used. /estimate is free.

The response envelope

Responses are JSON of the form {"ok": ..., "data": ..., "error": {...}}. The page’s SDK (sdk.js) decides success from the HTTP status, not from ok: on a 2xx it returns data; otherwise it raises an error whose message is error.message (or the HTTP status text when the body carries none), with status, code (error.code) and details (error.details) attached. A body that is not JSON is treated as {}.

{ "ok": true,  "data": { ... } }
{ "ok": false, "error": { "code": "...", "message": "...", "details": { ... } } }

Error codes

Branch on the HTTP status and error.code, show error.message. The page handles 401 and 402 by name; the rest reach it as a generic failure (“The run failed: ...”).

HTTPerror.codeWhat it means / what to do
400VALIDATION_ERRORThe body is not a JSON object, a required field (task, code, facts) is missing, or a declared field has the wrong type. Every field is a string; sending facts or audit_handoff as an object is the usual cause.
401UNAUTHORIZEDNo token, or one that expired or was revoked. The page forgets the signed-in user and says “Your session expired - sign in again.” Get a new token.
402INSUFFICIENT_CREDITSThe balance cannot cover the run. The page says “Not enough credits for this run.” with a top-up link, and tries never to submit into a 402: it disables the run button when the /me balance is below the estimate’s min_credits.
404NOT_FOUNDA wrong path, or a job id that does not belong to this token.
409CONFLICTAn Idempotency-Key replayed with a different body. Send the original body, or use a new key (the page bumps its :a<attempt> suffix for a retry).
429RATE_LIMITEDToo many requests. Back off with a delay; do not tight-loop, and never resend a metered run without its original Idempotency-Key.

Failures that do not arrive as an HTTP status:

WhatWhat happens
event: error on /run-streamThe SDK raises an error carrying the event’s message, code and job_id.
Job status: "failed"Polling stops; the job’s error field says why.
Polling past 180 sThe SDK’s own client-side limit (waitForJob, 1 s interval, 180 000 ms default): it throws “job timed out”. The job itself is not cancelled.

The task field: two lanes

task routes the run, and it takes exactly two values: "audit" and "fix". Each lane has its own reply contract, and a reply never mixes them. The skill’s instructions tell the model to do the audit lane when task is missing or anything else (and the page’s own Gokit.buildInput turns any lane that is not "fix" into "audit"), so a typo silently gets you an audit: always send one of the two values.

taskWhat it doesverdictUse it when
"audit"Reviews the component the way a senior Go engineer reviews concurrent code before it goes to production: says what the code does and what runs concurrently in it, rates the four areas (lifecycle, shared_state, channels, cancellation) as sound, fragile or broken, confirms or dismisses every page flag, adds what text rules cannot see, and gives the verbatim evidence and exact before/after edits for each finding, listed in the order to apply them — races and deadlocks first, then leaks, then dropped cancellation and errors, then hygiene — with a plan that ends in go vet ./... and go test -race ./....blocked | needs_work | readyBefore shipping: will it race, deadlock or leak goroutines, and what should change first.
"fix"Writes the fixed version of the pasted code: the whole of every Go file it changes (and at most one new file), with locks or channels around shared state, Add before go, one closer per channel, buffered result channels and a context every goroutine can be stopped with — plus a changes list tied to the page flags, the verify steps (the go vet and go test -race commands and the test to write), and what it left open. No public signature changes unless the note asks for it.fixed | partialWhen you want the code changed, not a list of findings: a component you already know is broken, or what is left after an audit.

The page chains the two: after an audit, “Apply every edit and fix the rest” puts every audit edit into the paste, switches to the fix lane on that edited paste and adds an audit_handoff string (below) with the audit’s verdict, headline and findings. Over the API that is a second run with task: "fix", the edited paste as code, facts recomputed from the edited paste, and that string. The page drops the handoff as soon as the paste is edited again.

Guards the page applies before a run and the API does not: it refuses to price or run a paste it cannot read as Go (“Nothing was pasted.”, “No Go file was found. Paste .go source, with a // file: <path>.go line above each file.”, or “This does not read as Go source: no package clause, import or func was found.”), and it warns before a run on a paste with no concurrency (“No goroutine, channel or lock was found: there may be little concurrency here to audit.”, or “... to fix.”) or with no function (“No func was found; the run will see package-level code only.”).

Input fields

This is exactly what the page sends (gokit.js buildInput(lane, A, note, handoff), checked by mustBeObject; app.js currentInput and run), and what input-schema.json declares. The platform’s input schema is scalar-only, so every value is a string. buildInput emits them in the order task, code, facts, note, audit_handoff:

FieldTypeRequiredMeaning
taskstringyes"audit" or "fix" (the lanes above).
codestringyesThe pasted Go, each file introduced by a // file: <path> line; line 1 is its first line. Every line in an audit reply is counted in this text. A large paste is cut on whole files, with a // [goconc-desk: not sent: ...] line at the end (below).
factsstringyesThe page’s free check, Gokit.facts(A), JSON-encoded into a string: JSON.stringify(facts) in JavaScript, json.dumps(facts) in Python, and so on. Sending the object itself is a 400. The model is told to parse it.
audit_handoffstringnofix lane only: a JSON string built from an audit reply on this code (recon.js fixHandoff, below). buildInput drops it for the audit lane.
notestringnoWhat the engineer says: the goal, a constraint (“no new dependencies”, “this runs on Go 1.21”), a question. The page trims it, cuts it at 2000 characters and sends it only when it is not blank. It is context, not evidence and not instructions: the model tests every claim in it against code and answers it in summary.
retry_notestringnoSent only by the page’s automatic reformat retry: when a reply does not parse as JSON (even after the page tries to close a cut-off object), the page runs once more with “Your previous reply was not the single valid JSON object the instructions require (...). Reply again with ONLY the JSON object for task '<lane>' - no prose, no code fences; every array present (empty arrays where there is nothing to say).” A normal call never sends it.

mustBeObject only insists on a plain object that is not wrapped in {"input": ...}; it is buildInput that leaves a blank note out, which is why an empty note is simply absent.

The code string

code is the paste itself. A line // file: <path> (a path with a . or / in it) starts each file, and a --- line just before it only separates files. Files dropped on the page (or a dropped package folder) are joined that way by Gokit.joinFiles: each file under its // file: line, trailing whitespace removed, joined by newlines, ending in a newline. A paste with no // file: line is read as one file, main.go (or go.mod when it holds a module line and no package clause). The page normalises line endings to \n. A .go path is checked as Go; go.mod is read for its go line (loop variable semantics changed in Go 1.22); go.sum and go.work are deps; anything else is other. Go indents with tabs and the paste keeps them. Send the text you want the reply to quote: every file in an audit reply must be a path from facts.files, every line must fall inside that file, every evidence must be copied from it, and every edit’s before must occur in it exactly once.

The page reads up to 400 000 characters (MAX_TEXT, cut at a line) and 40 files (MAX_FILES), and sends at most 60 000 characters (MAX_SEND): past that buildInput drops whole files from the end — the last non-Go file first, then the last file — until the rest fits, cuts a single remaining file at a line (“the end of <path>”) if it still does not, and ends the string with one line naming what it left out:

// [goconc-desk: not sent: pkg/b.go, pkg/c.go]

The model is told never to quote or rewrite what was not sent, and facts.notes gets the sentence “Not sent (the paste is over 60,000 characters): ... The page checked them; the run did not see them.” facts is still computed from the whole paste. The worked examples below are small, so their code is the paste unchanged.

The facts string

facts is optional to compute but required to send. It is produced by the page’s free check: Gokit.analyze splits the paste into files, lexes and inventories them and runs the rules, and Gokit.facts(A) turns that analysis into the object below (it does not depend on the lane: an audit and a fix run of the same paste carry the same facts). It is how the model knows which flags to answer, which file paths and line ranges to name, and which functions must survive. Its top-level keys, in the order built:

KeyContents
componentgo_version (from the go line of go.mod, or null when go.mod was not pasted), go_files, primitives (of sync, sync/atomic, context, golang.org/x/sync/errgroup, golang.org/x/sync/semaphore, time, as imported), goroutines and handlers (counts).
filesEvery file: path (use it verbatim), kind (go, gomod, deps, other), line and end_line (the paste lines of the file’s content, without its // file: line).
functionsEvery func and method (up to 200): name (Type.Method for methods), file, line.
handlersHTTP handlers: name, file, line, kind (net/http, gin, echo).
goroutinesEvery go statement and errgroup Go (up to 150): file, line, func (where it starts), kind (literal, call, errgroup), target, in_loop.
channelsUp to 100: name, file, line, made (created with make in the paste), buffered, size, closed (a close of that name appears somewhere in the paste).
locksEvery Lock and RLock (up to 150): target, kind, file, line, func, unlock (deferred, explicit, mismatched, none).
waitgroups, contextsEvery WaitGroup (name, file, line) and every context.WithCancel/WithTimeout/WithDeadline (up to 100: file, line, func, kind, cancel — used, never called, discarded).
flagsEvery flag the page raised: uid (F1, F2, ... in severity, file, then line order), id (GC-01..GC-24), sev (high | medium | low | info), file, line, text, detail. An audit must answer every non-info flag by its uid; a fix must close every high and medium flag or name it in open.
verdict_candidateThe audit verdict the flags point at (the page’s own reading): blocked with any high flag, needs_work with any medium, otherwise ready.
notesPlain sentences about what was not read or not sent (empty for a paste that fits).

The simplest source is gokit.js itself: the page loads it from /gokit.js, and it also runs in Node (it sets module.exports). This script prints what the page would send as code and facts (with the 60 000-character cut applied), with the facts decoded so you can read them; the step 4 samples compact them back into the page’s string byte for byte. Save the paste as crawler-paste.txt and redirect the output into crawler-check.json:

// build-check.js -- run: node build-check.js crawler-paste.txt > crawler-check.json   (gokit.js saved next to it)
const fs = require("fs");
const Gokit = require("./gokit.js");
const A = Gokit.analyze(fs.readFileSync(process.argv[2], "utf8"));
if (!A.ok) { console.error(A.errors.join("\n")); process.exit(1); }
const inp = Gokit.buildInput("audit", A, "", null);          // code and facts do not depend on the lane
console.log(JSON.stringify({ code: inp.code, facts: JSON.parse(inp.facts) }, null, 2));
const real = A.flags.filter((f) => f.sev !== "info");
console.error(A.files.length + " file(s), " + A.goroutines.length + " goroutine(s), " + A.handlers.length + " handler(s); " +
  real.length + " flags (" + A.counts.high + " high, " + A.counts.medium + " medium, " + A.counts.low + " low); verdict_candidate " + A.candidate);

For the crawler example it reports 2 file(s), 1 goroutine(s), 0 handler(s); 7 flags (6 high, 0 medium, 1 low); verdict_candidate blocked; for the pipeline example, 2 file(s), 3 goroutine(s), 0 handler(s); 4 flags (3 high, 1 medium, 0 low); verdict_candidate blocked. For both worked examples its code and facts are exactly what the page sends.

Without the page’s check. The platform only checks that facts is a string, so a minimal object works — "{}", or a hand-built one such as {"flags": [], "notes": ["No page check was run; read the code directly."]} — and the model still reads code. But you lose what the check adds: with no flags there is nothing to confirm, dismiss or close, with no files there are no paths and line ranges to name, and with no functions nothing says what must survive a fix. Build it with gokit.js as above when you can.

The audit_handoff string

Only for task: "fix", and only for the code the audit was about with every audit edit applied. The page builds both from the normalised audit reply res (recon.js normalize, below) and the analysis A of the audited paste, in recon.js fixHandoff:

const run = Recon.applyFindings(A, res.findings, Gokit);   // every finding's edits, in order, each on top of the last
JSON.stringify({ verdict: res.verdict, headline: res.headline,
  findings: res.findings.map(f => ({ id: f.id, severity: f.severity, file: f.file, line: f.line, title: f.title, why: f.why, edits: f.edits.length })),
  applied: /* edits that applied */, failed: /* edits that did not */ })
// run.A.text is the edited paste: it becomes code, and facts is recomputed from it

That is {"verdict", "headline", "findings": [{id, severity, file, line, title, why, edits}], "applied", "failed"}, where each finding’s edits is the number of edits it carried. applyFindings applies every finding’s edits with Gokit.applyEdits, in the order listed: each before must occur exactly once in its file as the earlier edits left it (leading and trailing blank lines ignored; an edit whose indentation differs from the file only in tabs versus spaces is matched line by line and re-indented to the file’s own tabs), or that edit is reported as failed and skipped. The handoff is a previous reply, not evidence: the model checks it against code and fixes what is left. This script does the whole step — the edited paste, its recomputed facts and the handoff string, in the same shape as build-check.js prints, so the samples read it the same way:

// handoff.js -- run: node handoff.js crawler-paste.txt crawler-reply.txt > crawler-fix-check.json   (gokit.js, recon.js next to it)
const fs = require("fs");
const Gokit = require("./gokit.js");
const Recon = require("./recon.js");
const A = Gokit.analyze(fs.readFileSync(process.argv[2], "utf8"));             // the audited paste
const res = Recon.normalize(Recon.parseResult(fs.readFileSync(process.argv[3], "utf8")), "audit");
const ho = Recon.fixHandoff(A, res);                                            // every edit, in order
const B = Gokit.analyze(ho.text);                                               // facts recomputed from the edited paste
const inp = Gokit.buildInput("fix", B, "", ho.handoff);
console.log(JSON.stringify({ code: inp.code, facts: JSON.parse(inp.facts), audit_handoff: inp.audit_handoff }, null, 2));
console.error(ho.applied + " edit(s) applied, " + ho.failed + " did not apply");

Save the audit’s output.output as crawler-reply.txt; for the crawler example it reports 5 edit(s) applied, 0 did not apply. The third worked example below is exactly that run.

The output contracts

The model is instructed (SKILL.md) to reply with one JSON object and nothing else — no prose, no code fences — with every array of its lane present ([] when empty). The run’s output text is that object. In both lanes it must ground everything in code: a function, type, caller, test, close, Unlock or Wait that is not in the paste is never claimed to exist — it goes into questions. It must never write a secret value: a line that holds a literal credential is never copied into the reply (not in evidence, not in an edit, not in a file), and no credential, real hostname or endpoint is invented. It must never change what the code does for its callers: same exported functions, names, parameters, results and values on the paths that worked, unless the note asks for it. Every page flag whose sev is not info is answered exactly once (audit lane: in flag_answers), and an info flag is never reported as a finding.

task: "audit"

task: "fix"

How the page reads a reply (recon.js parseResult and normalize): it strips a code fence and parses from the first { to the last }. A reply is read as the lane that was asked for, unless it names the other lane and carries that lane’s body (a files array for fix, a findings array for audit). An unknown verdict shows as needs_work or partial and counts as a disagreement; an unknown severity becomes medium; ids like GC7 or gc 7 become GC-07; flags keeps only F<n> values; findings with no file, evidence and title are dropped; file may also arrive as path, a change’s function as func and its change as what, and an open item as path; questions is capped at 10, plan at 30 and verify at 20. An audit reply carries 9 sections (verdict, headline, component, areas, findings, flag_answers, plan, questions, summary) and a fix reply 8 (verdict, headline, files, changes, verify, open, questions, summary), which is what the page counts when it recovers a partial reply.

Worked examples

All three are examples the page ships (the example buttons on the app page; the full pastes are in /example.js, ids crawler and pipeline), built exactly as the page builds them: Gokit.buildInput(lane, Gokit.analyze(text), note, handoff), with the real model reply the page replays for each (example-result.js, keys crawler, pipeline and crawler:fix). The pastes are shown whole; the request bodies cut the long code, facts and audit_handoff strings with a visible ..., and the replies are pretty-printed for display. Every value shown is copied from the input or the reply. Neither paste holds a credential.

Audit: a link checker that races on one map and copies its mutex

Two files, go.mod (go 1.21) and crawler/crawler.go: a link checker for the Kestrelmoor docs site. CheckAll starts one goroutine per URL, calls wg.Add(1) inside it, reads the loop variable u, and writes a results map and a failures counter from every goroutine with no lock; check throws away the cancel func of its context.WithTimeout, and Checked has a value receiver on a struct that holds a sync.Mutex. The page’s free check raises 7 flags (6 high, 1 low) (plus the GC-24 context flag); its own reading of the verdict is blocked. The engineer’s note: “The link checker sometimes crashes with 'concurrent map writes' and the failure count is always wrong. What do we fix before it runs on the whole docs site?”

The code string, whole: 1,581 characters, 77 lines ending in a newline. Save it as crawler-paste.txt and run node build-check.js crawler-paste.txt > crawler-check.json:

// file: go.mod
module example.com/linkcheck

go 1.21
// file: crawler/crawler.go
package crawler

import (
	"context"
	"fmt"
	"net/http"
	"sync"
	"time"
)

// Result is what one URL check found.
type Result struct {
	URL    string
	Status int
	Err    error
}

// Checker checks the links of the Kestrelmoor docs site.
type Checker struct {
	client  *http.Client
	mu      sync.Mutex
	checked int
}

func NewChecker() *Checker {
	return &Checker{client: &http.Client{Timeout: 10 * time.Second}}
}

// CheckAll fetches every URL at once and returns the result of each.
func (c *Checker) CheckAll(ctx context.Context, urls []string) map[string]Result {
	results := make(map[string]Result)
	var wg sync.WaitGroup
	failures := 0
	for _, u := range urls {
		go func() {
			wg.Add(1)
			defer wg.Done()
			res := c.check(ctx, u)
			results[u] = res
			if res.Err != nil {
				failures++
			}
		}()
	}
	wg.Wait()
	fmt.Printf("checked %d urls, %d failures\n", len(urls), failures)
	return results
}

func (c *Checker) check(ctx context.Context, url string) Result {
	ctx, _ = context.WithTimeout(ctx, 5*time.Second)
	req, err := http.NewRequestWithContext(ctx, http.MethodHead, url, nil)
	if err != nil {
		return Result{URL: url, Err: err}
	}
	resp, err := c.client.Do(req)
	if err != nil {
		return Result{URL: url, Err: err}
	}
	resp.Body.Close()
	c.mu.Lock()
	c.checked++
	c.mu.Unlock()
	return Result{URL: url, Status: resp.StatusCode}
}

// Checked reports how many URLs have been checked so far.
func (c Checker) Checked() int {
	c.mu.Lock()
	defer c.mu.Unlock()
	return c.checked
}

Its facts, decoded (the request carries this object as one compact JSON string):

{
  "component": {
    "go_version": "1.21",
    "go_files": 1,
    "primitives": [
      "sync",
      "context",
      "time"
    ],
    "goroutines": 1,
    "handlers": 0
  },
  "files": [
    {
      "path": "go.mod",
      "kind": "gomod",
      "line": 2,
      "end_line": 4
    },
    {
      "path": "crawler/crawler.go",
      "kind": "go",
      "line": 6,
      "end_line": 78
    }
  ],
  "functions": [
    {
      "name": "NewChecker",
      "file": "crawler/crawler.go",
      "line": 30
    },
    {
      "name": "Checker.CheckAll",
      "file": "crawler/crawler.go",
      "line": 35
    },
    {
      "name": "Checker.check",
      "file": "crawler/crawler.go",
      "line": 55
    },
    {
      "name": "Checker.Checked",
      "file": "crawler/crawler.go",
      "line": 73
    }
  ],
  "handlers": [],
  "goroutines": [
    {
      "file": "crawler/crawler.go",
      "line": 40,
      "func": "Checker.CheckAll",
      "kind": "literal",
      "target": null,
      "in_loop": true
    }
  ],
  "channels": [],
  "locks": [
    {
      "target": "c.mu",
      "kind": "Lock",
      "file": "crawler/crawler.go",
      "line": 66,
      "func": "Checker.check",
      "unlock": "explicit"
    },
    {
      "target": "c.mu",
      "kind": "Lock",
      "file": "crawler/crawler.go",
      "line": 74,
      "func": "Checker.Checked",
      "unlock": "deferred"
    }
  ],
  "waitgroups": [
    {
      "name": "wg",
      "file": "crawler/crawler.go",
      "line": 37
    }
  ],
  "contexts": [
    {
      "file": "crawler/crawler.go",
      "line": 56,
      "func": "Checker.check",
      "kind": "WithTimeout",
      "cancel": "discarded"
    }
  ],
  "flags": [
    {
      "uid": "F1",
      "id": "GC-02",
      "sev": "high",
      "file": "crawler/crawler.go",
      "line": 41,
      "text": "wg.Add runs inside the goroutine it counts",
      "detail": "Wait can run before the goroutine is scheduled, see a zero counter and return while work is still going on. Call Add before the go statement."
    },
    {
      "uid": "F2",
      "id": "GC-01",
      "sev": "high",
      "file": "crawler/crawler.go",
      "line": 43,
      "text": "the goroutine reads the loop variable u from the enclosing for",
      "detail": "go.mod says go 1.21: before Go 1.22 every iteration shares one u, so the goroutines see whatever value it has when they run - usually the last. Pass it as an argument (go func(u T) {...}(u)) or copy it (u := u) inside the loop."
    },
    {
      "uid": "F3",
      "id": "GC-07",
      "sev": "high",
      "file": "crawler/crawler.go",
      "line": 44,
      "text": "map results is written from goroutines started in a loop with no lock",
      "detail": "Two goroutines writing a Go map at once is a fatal runtime error (concurrent map writes), not just a wrong value. Guard it with a sync.Mutex held around every read and write, give each goroutine its own result and merge after Wait, or send results over a channel."
    },
    {
      "uid": "F4",
      "id": "GC-08",
      "sev": "high",
      "file": "crawler/crawler.go",
      "line": 46,
      "text": "failures++ runs from goroutines started in a loop with no lock or atomic",
      "detail": "Read-modify-write on a shared variable from several goroutines is a data race: updates are lost and go test -race reports it. Use sync/atomic (atomic.Int64 and .Add), hold a mutex, or count per goroutine and sum after Wait."
    },
    {
      "uid": "F5",
      "id": "GC-15",
      "sev": "high",
      "file": "crawler/crawler.go",
      "line": 56,
      "text": "the cancel func of context.WithTimeout is thrown away",
      "detail": "The context and its timer stay alive until the parent is cancelled (go vet lostcancel): under load that is a leak per call, and nothing can stop the work early. Keep it: ctx, cancel := ... and defer cancel()."
    },
    {
      "uid": "F6",
      "id": "GC-04",
      "sev": "high",
      "file": "crawler/crawler.go",
      "line": 73,
      "text": "Checker.Checked has a value receiver, so it copies Checker's sync.Mutex",
      "detail": "The method locks its own copy of the mutex, so two callers never exclude each other and the struct's state races (go vet copylocks). Use a pointer receiver: func (c *Checker)."
    },
    {
      "uid": "F7",
      "id": "GC-19",
      "sev": "low",
      "file": "crawler/crawler.go",
      "line": 40,
      "text": "one goroutine per item of urls, with no limit",
      "detail": "Fan-out grows with the input: 10,000 items start 10,000 goroutines, each holding a connection or a file. Bound it - errgroup's SetLimit(n), a buffered semaphore channel, or a fixed pool of workers reading a jobs channel."
    },
    {
      "uid": "F8",
      "id": "GC-24",
      "sev": "info",
      "file": "crawler/crawler.go",
      "line": 6,
      "text": "context: 1 goroutine(s), 0 channel(s), 2 lock call(s), 1 derived context(s); go.mod says go 1.21",
      "detail": "Also read: go.mod (gomod). Only what is pasted is checked: a Close, Unlock or Wait that happens in a file you did not paste is outside the check. The page does not run the race detector - go test -race is the ground truth."
    }
  ],
  "verdict_candidate": "blocked",
  "notes": []
}

The request body (POST /estimate, then POST /run or /run-stream with Idempotency-Key: goconc-desk:audit:fb06d86d-1942:a1 as the page sends it):

{
  "task": "audit",
  "code": "// file: go.mod\nmodule example.com/linkcheck\n\ngo 1.21\n// file: crawler/crawler.go\npackage crawler\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"sync\"\n\t\"time\"\n)\n\n// Result is ...",
  "facts": "{\"component\":{\"go_version\":\"1.21\",\"go_files\":1,\"primitives\":[\"sync\",\"context\",\"time\"],\"goroutines\":1,\"handlers\":0},\"files\":[{\"path\":\"go.mod\",\"kind\":\"g...",
  "note": "The link checker sometimes crashes with 'concurrent map writes' and the failure count is always wrong. What do we fix before it runs on the whole docs site?"
}

The reply (output.output), whole:

{
  "task": "audit",
  "verdict": "blocked",
  "headline": "CheckAll spawns unsynchronized goroutines that race on a shared map and counter and can return before they finish, which is the concurrent-map-write crash and the wrong failure count you're seeing.",
  "component": "Checker.CheckAll fans out one goroutine per URL to HEAD-check a Kestrelmoor docs link list concurrently, collecting each Result into a shared map and a failure count before returning; check derives a 5s per-call timeout from the caller's context and updates a shared counter under a mutex.",
  "areas": [
    {
      "area": "lifecycle",
      "status": "broken",
      "note": "wg.Add(1) runs inside the goroutine it counts and the loop variable u is captured by reference on Go 1.21, so goroutines are miscounted and can check the wrong URL or let Wait return before all checks finish."
    },
    {
      "area": "shared_state",
      "status": "broken",
      "note": "The results map and failures counter are written from every goroutine with no lock, and Checked's value receiver copies the mutex meant to guard checked."
    },
    {
      "area": "channels",
      "status": "sound",
      "note": "No channels are used anywhere in this component, so there is nothing to close, range over or select on."
    },
    {
      "area": "cancellation",
      "status": "broken",
      "note": "check discards the cancel func returned by context.WithTimeout, leaking a timer on every call."
    }
  ],
  "findings": [
    {
      "id": "GC-02",
      "flags": [
        "F1",
        "F2"
      ],
      "file": "crawler/crawler.go",
      "line": 41,
      "severity": "high",
      "area": "lifecycle",
      "title": "wg.Add runs inside the goroutine, and it closes over the shared loop variable u",
      "evidence": "for _, u := range urls {\n\tgo func() {\n\t\twg.Add(1)",
      "why": "wg.Add(1) runs inside the goroutine, so wg.Wait() on line 50 can see a zero counter before any goroutine has registered and return while checks are still in flight -- the caller then holds a results map and failures count that leftover goroutines are still writing after CheckAll has returned. Because the goroutine reads the loop variable u directly (not a parameter or a copy) and go.mod pins Go 1.21, every goroutine can also see whatever value u has when it runs instead of the URL it was launched for, so some URLs are checked under the wrong key and others never get checked.",
      "edits": [
        {
          "before": "\tfor _, u := range urls {\n\t\tgo func() {\n\t\t\twg.Add(1)\n\t\t\tdefer wg.Done()\n\t\t\tres := c.check(ctx, u)\n\t\t\tresults[u] = res\n\t\t\tif res.Err != nil {\n\t\t\t\tfailures++\n\t\t\t}\n\t\t}()\n\t}",
          "after": "\tfor _, u := range urls {\n\t\twg.Add(1)\n\t\tgo func(u string) {\n\t\t\tdefer wg.Done()\n\t\t\tres := c.check(ctx, u)\n\t\t\tresults[u] = res\n\t\t\tif res.Err != nil {\n\t\t\t\tfailures++\n\t\t\t}\n\t\t}(u)\n\t}"
        }
      ]
    },
    {
      "id": "GC-07",
      "flags": [
        "F3",
        "F4"
      ],
      "file": "crawler/crawler.go",
      "line": 44,
      "severity": "high",
      "area": "shared_state",
      "title": "the results map and the failures counter are written from every goroutine with no lock",
      "evidence": "results[u] = res\nif res.Err != nil {\n\tfailures++",
      "why": "results[u] = res runs in every one of the per-URL goroutines with no lock: two goroutines writing the same Go map at once is a fatal 'concurrent map writes' runtime error, not just a wrong value, which matches the crash you're seeing. failures++ is a read-modify-write on a shared variable from several goroutines -- a data race that go test -race reports -- so increments are silently lost under load, which is why the printed failure count is always wrong.",
      "edits": [
        {
          "before": "\tvar wg sync.WaitGroup",
          "after": "\tvar mu sync.Mutex\n\tvar wg sync.WaitGroup"
        },
        {
          "before": "\tfor _, u := range urls {\n\t\twg.Add(1)\n\t\tgo func(u string) {\n\t\t\tdefer wg.Done()\n\t\t\tres := c.check(ctx, u)\n\t\t\tresults[u] = res\n\t\t\tif res.Err != nil {\n\t\t\t\tfailures++\n\t\t\t}\n\t\t}(u)\n\t}",
          "after": "\tfor _, u := range urls {\n\t\twg.Add(1)\n\t\tgo func(u string) {\n\t\t\tdefer wg.Done()\n\t\t\tres := c.check(ctx, u)\n\t\t\tmu.Lock()\n\t\t\tresults[u] = res\n\t\t\tif res.Err != nil {\n\t\t\t\tfailures++\n\t\t\t}\n\t\t\tmu.Unlock()\n\t\t}(u)\n\t}"
        }
      ]
    },
    {
      "id": "GC-04",
      "flags": [
        "F6"
      ],
      "file": "crawler/crawler.go",
      "line": 73,
      "severity": "high",
      "area": "shared_state",
      "title": "Checker.Checked has a value receiver and copies the mutex it locks",
      "evidence": "func (c Checker) Checked() int {",
      "why": "Checked has a value receiver, so c -- including its sync.Mutex -- is copied on every call. Lock/Unlock inside Checked then operate on that copy, not the Checker instance's mutex, so a call to Checked() no longer excludes a concurrent write to checked in check(): go vet's copylocks check flags this, and Checked() can read checked while another goroutine is mid-increment on the real struct.",
      "edits": [
        {
          "before": "func (c Checker) Checked() int {",
          "after": "func (c *Checker) Checked() int {"
        }
      ]
    },
    {
      "id": "GC-15",
      "flags": [
        "F5"
      ],
      "file": "crawler/crawler.go",
      "line": 56,
      "severity": "high",
      "area": "cancellation",
      "title": "the cancel func of context.WithTimeout is discarded",
      "evidence": "ctx, _ = context.WithTimeout(ctx, 5*time.Second)",
      "why": "The context and its timer stay referenced until the parent ctx is done (go vet's lostcancel check flags this): with one call per URL and no cancel, this leaks one timer per check() call for as long as the crawl runs, and nothing can free it early once the request completes.",
      "edits": [
        {
          "before": "\tctx, _ = context.WithTimeout(ctx, 5*time.Second)",
          "after": "\tctx, cancel := context.WithTimeout(ctx, 5*time.Second)\n\tdefer cancel()"
        }
      ]
    },
    {
      "id": "GC-19",
      "flags": [
        "F7"
      ],
      "file": "crawler/crawler.go",
      "line": 40,
      "severity": "low",
      "area": "lifecycle",
      "title": "one goroutine per URL with no limit",
      "evidence": "for _, u := range urls {\n\tgo func() {",
      "why": "CheckAll starts one goroutine per URL with no bound: fine for a short list, but pointed at the whole docs site it opens as many outbound HTTP connections at once as there are URLs, which can exhaust file descriptors or trip the target site's rate limiting. Bound it with errgroup's SetLimit(n) or a fixed-size worker pool reading a jobs channel before running this at scale; the fix is outside these findings' edits since it changes CheckAll's structure rather than one line.",
      "edits": []
    }
  ],
  "flag_answers": [
    {
      "flag": "F1",
      "status": "confirmed",
      "reason": "wg.Add(1) is called from inside the goroutine it counts, so Wait can race ahead of it."
    },
    {
      "flag": "F2",
      "status": "confirmed",
      "reason": "the goroutine reads the loop variable u directly and go.mod pins Go 1.21, which shares one u across iterations."
    },
    {
      "flag": "F3",
      "status": "confirmed",
      "reason": "results[u] = res writes the shared map from every goroutine with no lock."
    },
    {
      "flag": "F4",
      "status": "confirmed",
      "reason": "failures++ is an unsynchronized read-modify-write from every goroutine."
    },
    {
      "flag": "F5",
      "status": "confirmed",
      "reason": "the cancel func returned by context.WithTimeout is assigned to _ and never called."
    },
    {
      "flag": "F6",
      "status": "confirmed",
      "reason": "Checked has a value receiver, so it locks a copy of Checker's mutex instead of the real one."
    },
    {
      "flag": "F7",
      "status": "confirmed",
      "reason": "CheckAll starts one goroutine per entry of urls with no limit on how many run at once."
    }
  ],
  "plan": [
    "In CheckAll, call wg.Add(1) before each go statement and pass u into the goroutine as a parameter (go func(u string) {...}(u)), so every goroutine is counted before it can finish and checks the URL it was launched for (GC-02, GC-01).",
    "Add a sync.Mutex in CheckAll and hold it around both results[u] = res and failures++ inside the goroutine, so the map write and the counter are serialized and the concurrent-map-write panic and the wrong failure count both go away (GC-07, GC-08).",
    "Change Checker.Checked to a pointer receiver (func (c *Checker) Checked() int) so its Lock/Unlock act on the real mutex instead of a copy (GC-04).",
    "In check, keep the cancel func from context.WithTimeout and defer cancel() (GC-15).",
    "Bound the number of concurrent checks (errgroup.SetLimit or a fixed worker pool) before pointing CheckAll at the whole docs site (GC-19).",
    "Run go vet ./... and go test -race ./... with a test that calls CheckAll concurrently against a local httptest.Server and asserts both no race and a correct failures count."
  ],
  "questions": [
    "Can go.mod's go line move to 1.22 or later? That makes each loop iteration's u its own variable by default, so the explicit func(u string) parameter becomes a belt-and-suspenders fix rather than a required one.",
    "Do any callers of CheckAll read the returned map or rely on Checked() while a previous CheckAll call might still be running in the background, or is CheckAll always awaited fully before the next call?"
  ],
  "summary": "The crash and the wrong count share one root cause: wg.Add(1) runs inside the goroutine it counts, and the results map and failures counter it starts are written from every goroutine with no lock, so Wait can return early and the map write races -- that is the 'concurrent map writes' panic -- while failures++ silently loses increments. Fix the launch (Add before go, pass u as a parameter) and guard results[u]=res and failures++ with one mutex; Checked's value receiver (it copies the guarding mutex) and check's discarded cancel func are separate high-severity bugs worth fixing in the same pass. Before running this against the whole docs site, also bound how many goroutines run at once -- right now it's one per URL with no limit."
}

Two findings each carry two flags (one change closes both), every one of the 7 flags is confirmed, and the low GC-19 finding has no edit because bounding the fan-out changes the structure of CheckAll rather than one line — which is what the handoff fix below takes on.

Fix: a CSV ingest pipeline that deadlocks, double-closes a channel and leaks a goroutine per timeout

Two files, go.mod (go 1.22) and ingest/pipeline.go. Parse feeds line numbers to workers over an unbuffered jobs channel that nothing closes, and every worker calls close(out); Lookup sends the fetch result on an unbuffered channel that its select abandons on timeout; Drain spins on a select with an empty default. The free check raises 4 flags (3 high, 1 medium); its reading of the verdict is blocked. This example has no audit before it, so it carries no audit_handoff. The engineer’s note: “Parse hangs in production and sometimes panics with 'close of closed channel'; Lookup leaks goroutines when the partner API is slow. Fix those two, no new dependencies. Leave Drain exactly as it is for now - the status team owns it and is rewriting it next sprint.”

The code string, whole: 1,472 characters, 81 lines ending in a newline. Save it as pipeline-paste.txt and run node build-check.js pipeline-paste.txt > pipeline-check.json:

// file: go.mod
module example.com/ingest

go 1.22
// file: ingest/pipeline.go
package ingest

import (
	"context"
	"errors"
	"strings"
	"sync"
	"time"
)

// Record is one parsed line of a partner's CSV feed.
type Record struct {
	Line   int
	Fields []string
}

// Parse splits lines into records using workers goroutines.
func Parse(lines []string, workers int) []Record {
	jobs := make(chan int)
	out := make(chan Record)
	var wg sync.WaitGroup
	for w := 0; w < workers; w++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for i := range jobs {
				out <- Record{Line: i + 1, Fields: strings.Split(lines[i], ",")}
			}
			close(out)
		}()
	}
	go func() {
		for i := range lines {
			jobs <- i
		}
	}()
	var recs []Record
	for r := range out {
		recs = append(recs, r)
	}
	wg.Wait()
	return recs
}

// Lookup asks the partner API for one record's account, giving up after timeout.
func Lookup(ctx context.Context, fetch func(context.Context, string) (string, error), id string, timeout time.Duration) (string, error) {
	res := make(chan string)
	go func() {
		acct, err := fetch(ctx, id)
		if err != nil {
			return
		}
		res <- acct
	}()
	select {
	case acct := <-res:
		return acct, nil
	case <-time.After(timeout):
		return "", errors.New("lookup timed out")
	}
}

// Drain empties a status channel without blocking the caller.
func Drain(status chan string) int {
	n := 0
	for {
		select {
		case <-status:
			n++
		default:
		}
		if n > 100 {
			return n
		}
	}
}

The flags in its facts, decoded (the other keys are trimmed for display):

{
  "component": {
    "go_version": "1.22",
    "go_files": 1,
    "primitives": [
      "sync",
      "context",
      "time"
    ],
    "goroutines": 3,
    "handlers": 0
  },
  "files": [
    {
      "path": "go.mod",
      "kind": "gomod",
      "line": 2,
      "end_line": 4
    },
    {
      "path": "ingest/pipeline.go",
      "kind": "go",
      "line": 6,
      "end_line": 82
    }
  ],
  ... [trimmed for display: functions, handlers, goroutines, channels, locks, waitgroups, contexts]
  "flags": [
    {
      "uid": "F1",
      "id": "GC-11",
      "sev": "high",
      "file": "ingest/pipeline.go",
      "line": 31,
      "text": "for ... range jobs never ends: nothing in the paste closes jobs",
      "detail": "A range over a channel only stops when the channel is closed. Here the loop blocks after the last value - the goroutine running it leaks, or the whole program deadlocks if it is the main one. The sender closes it (defer close(jobs)) once it is done sending; with several senders, close after their WaitGroup."
    },
    {
      "uid": "F2",
      "id": "GC-12",
      "sev": "high",
      "file": "ingest/pipeline.go",
      "line": 34,
      "text": "close(out) runs in a goroutine started in a loop",
      "detail": "Every iteration's goroutine closes the same channel: the second close panics (close of closed channel), and a send after the first panics too. Close once, from the single sender, after a WaitGroup says every sender is done."
    },
    {
      "uid": "F3",
      "id": "GC-10",
      "sev": "high",
      "file": "ingest/pipeline.go",
      "line": 58,
      "text": "the goroutine's send on res blocks forever once the select leaves by its other case",
      "detail": "res is unbuffered, so the send only completes if someone receives. When the select at line 60 takes the timeout or ctx.Done() case, nobody ever will: one goroutine (and everything it holds) leaks per call. make(chan string, 1) lets the send complete and the goroutine exit."
    },
    {
      "uid": "F4",
      "id": "GC-14",
      "sev": "medium",
      "file": "ingest/pipeline.go",
      "line": 72,
      "text": "the select has an empty default inside a loop, so the loop spins",
      "detail": "When no channel is ready the default runs at once and the loop comes straight back: one core at 100% doing nothing. Drop the default so the select blocks, or block on a ticker or ctx.Done()."
    },
    {
      "uid": "F5",
      "id": "GC-24",
      "sev": "info",
      "file": "ingest/pipeline.go",
      "line": 6,
      "text": "context: 3 goroutine(s), 4 channel(s), 0 lock call(s), 0 derived context(s); go.mod says go 1.22",
      "detail": "Also read: go.mod (gomod). Only what is pasted is checked: a Close, Unlock or Wait that happens in a file you did not paste is outside the check. The page does not run the race detector - go test -race is the ground truth."
    }
  ],
  "verdict_candidate": "blocked",
  "notes": []
}

The request body (Idempotency-Key: goconc-desk:fix:fed85a1a-167f:a1):

{
  "task": "fix",
  "code": "// file: go.mod\nmodule example.com/ingest\n\ngo 1.22\n// file: ingest/pipeline.go\npackage ingest\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n)\n\n// Record is on...",
  "facts": "{\"component\":{\"go_version\":\"1.22\",\"go_files\":1,\"primitives\":[\"sync\",\"context\",\"time\"],\"goroutines\":3,\"handlers\":0},\"files\":[{\"path\":\"go.mod\",\"kind\":\"g...",
  "note": "Parse hangs in production and sometimes panics with 'close of closed channel'; Lookup leaks goroutines when the partner API is slow. Fix those two, no new dependencies. Leave Drain exactly as it is for now - the status team owns it and is rewriting it next sprint."
}

The reply (output.output), whole; files[0].content is the whole fixed file as one JSON string:

{
  "task": "fix",
  "verdict": "partial",
  "headline": "Parse's channel lifecycle and Lookup's goroutine leak are fixed; Drain's busy-spin is left open at the engineer's request.",
  "files": [
    {
      "path": "ingest/pipeline.go",
      "content": "package ingest\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n)\n\n// Record is one parsed line of a partner's CSV feed.\ntype Record struct {\n\tLine   int\n\tFields []string\n}\n\n// Parse splits lines into records using workers goroutines.\nfunc Parse(lines []string, workers int) []Record {\n\tjobs := make(chan int)\n\tout := make(chan Record)\n\tvar wg sync.WaitGroup\n\tfor w := 0; w < workers; w++ {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tfor i := range jobs {\n\t\t\t\tout <- Record{Line: i + 1, Fields: strings.Split(lines[i], \",\")}\n\t\t\t}\n\t\t}()\n\t}\n\tgo func() {\n\t\tfor i := range lines {\n\t\t\tjobs <- i\n\t\t}\n\t\tclose(jobs)\n\t}()\n\tgo func() {\n\t\twg.Wait()\n\t\tclose(out)\n\t}()\n\tvar recs []Record\n\tfor r := range out {\n\t\trecs = append(recs, r)\n\t}\n\treturn recs\n}\n\n// Lookup asks the partner API for one record's account, giving up after timeout.\nfunc Lookup(ctx context.Context, fetch func(context.Context, string) (string, error), id string, timeout time.Duration) (string, error) {\n\tres := make(chan string, 1)\n\tgo func() {\n\t\tacct, err := fetch(ctx, id)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tres <- acct\n\t}()\n\tselect {\n\tcase acct := <-res:\n\t\treturn acct, nil\n\tcase <-time.After(timeout):\n\t\treturn \"\", errors.New(\"lookup timed out\")\n\t}\n}\n\n// Drain empties a status channel without blocking the caller.\nfunc Drain(status chan string) int {\n\tn := 0\n\tfor {\n\t\tselect {\n\t\tcase <-status:\n\t\t\tn++\n\t\tdefault:\n\t\t}\n\t\tif n > 100 {\n\t\t\treturn n\n\t\t}\n\t}\n}\n"
    }
  ],
  "changes": [
    {
      "file": "ingest/pipeline.go",
      "function": "Parse",
      "flags": [
        "F1"
      ],
      "change": "the goroutine that feeds jobs now closes jobs itself once it has sent every line index",
      "why": "each worker's for i := range jobs now ends when jobs closes instead of blocking forever after the last job"
    },
    {
      "file": "ingest/pipeline.go",
      "function": "Parse",
      "flags": [
        "F2"
      ],
      "change": "close(out) moved out of the per-worker goroutine into one dedicated goroutine that waits on wg before closing",
      "why": "out is closed exactly once, after every worker has stopped sending, so a second close can no longer panic and no send can race the close"
    },
    {
      "file": "ingest/pipeline.go",
      "function": "Lookup",
      "flags": [
        "F3"
      ],
      "change": "res is now made with a buffer of 1 (make(chan string, 1)) instead of unbuffered",
      "why": "the fetch goroutine's send on res always completes even when Lookup has already returned via the timeout case, so the goroutine exits instead of leaking on every slow call"
    }
  ],
  "verify": [
    "go vet ./...",
    "go test -race ./...",
    "add a test that calls Parse(lines, workers) with workers > 1 on a few dozen lines and asserts it returns within a short deadline (e.g. via a goroutine + select/time.After) with len(recs) == len(lines); run it with -race - this is what would have caught the range-over-jobs deadlock and the double close(out) panic",
    "add a test for Lookup with a fetch func that blocks past timeout, call Lookup in a loop (go test -race -count=50) and check runtime.NumGoroutine() settles back down afterward - this is what would have caught the leaked goroutine on res"
  ],
  "open": [
    {
      "item": "Drain's select still busy-spins on its empty default inside the for loop (GC-14), burning a full core while it waits for status",
      "flags": [
        "F4"
      ],
      "reason": "the note asks to leave Drain exactly as pasted - the status team owns it and is rewriting it next sprint, so changing it here is outside this fix's agreed scope"
    }
  ],
  "questions": [
    "Lookup's goroutine still returns silently when fetch errs (no send on res), so a real fetch error is never surfaced - the caller just sees \"lookup timed out\" after the full timeout elapses. Is that acceptable, or should the real error be propagated back (would need a small result type, outside the leak fix the note asked for)?",
    "Parse assumes workers >= 1. With workers == 0 and a non-empty lines, the feeder goroutine would now block forever sending to the unbuffered jobs channel (nothing reads it) while Parse itself returns an empty slice as soon as out closes - does the caller guarantee workers > 0, or should Parse validate it?"
  ],
  "summary": "The note's two symptoms both check out against the code: nothing closed jobs, so every worker's range over it blocked forever, and every worker's own close(out) meant the second worker to finish would panic with close of closed channel; both are fixed by closing jobs from its sole sender and closing out once, from a goroutine gated on the WaitGroup. Lookup's send on res leaked one goroutine per slow call because res was unbuffered and abandoned once the timeout fired; buffering it by 1 lets that send always complete. No new dependencies were added, only ingest/pipeline.go changed, and Drain was left exactly as pasted per the note - its GC-14 busy-spin (F4) is carried in open."
}

That content, decoded — what the page puts back into the paste under // file: ingest/pipeline.go:

package ingest

import (
	"context"
	"errors"
	"strings"
	"sync"
	"time"
)

// Record is one parsed line of a partner's CSV feed.
type Record struct {
	Line   int
	Fields []string
}

// Parse splits lines into records using workers goroutines.
func Parse(lines []string, workers int) []Record {
	jobs := make(chan int)
	out := make(chan Record)
	var wg sync.WaitGroup
	for w := 0; w < workers; w++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for i := range jobs {
				out <- Record{Line: i + 1, Fields: strings.Split(lines[i], ",")}
			}
		}()
	}
	go func() {
		for i := range lines {
			jobs <- i
		}
		close(jobs)
	}()
	go func() {
		wg.Wait()
		close(out)
	}()
	var recs []Record
	for r := range out {
		recs = append(recs, r)
	}
	return recs
}

// Lookup asks the partner API for one record's account, giving up after timeout.
func Lookup(ctx context.Context, fetch func(context.Context, string) (string, error), id string, timeout time.Duration) (string, error) {
	res := make(chan string, 1)
	go func() {
		acct, err := fetch(ctx, id)
		if err != nil {
			return
		}
		res <- acct
	}()
	select {
	case acct := <-res:
		return acct, nil
	case <-time.After(timeout):
		return "", errors.New("lookup timed out")
	}
}

// Drain empties a status channel without blocking the caller.
func Drain(status chan string) int {
	n := 0
	for {
		select {
		case <-status:
			n++
		default:
		}
		if n > 100 {
			return n
		}
	}
}

The note asked to leave Drain alone, so its GC-14 flag (F4) is carried in open and the verdict is partial, not fixed.

Audit, then fix: the crawler audit handed to the fix lane

After the crawler audit above, the page’s “Apply every edit and fix the rest” ran Recon.fixHandoff on it: 5 edit(s) applied, 0 did not apply. That edited paste is the new code (1,659 characters); its facts, recomputed, raise 1 flag (1 low), and verdict_candidate is now ready. The note is carried over unchanged. CheckAll in the edited paste:

// CheckAll fetches every URL at once and returns the result of each.
func (c *Checker) CheckAll(ctx context.Context, urls []string) map[string]Result {
	results := make(map[string]Result)
	var mu sync.Mutex
	var wg sync.WaitGroup
	failures := 0
	for _, u := range urls {
		wg.Add(1)
		go func(u string) {
			defer wg.Done()
			res := c.check(ctx, u)
			mu.Lock()
			results[u] = res
			if res.Err != nil {
				failures++
			}
			mu.Unlock()
		}(u)
	}
	wg.Wait()
	fmt.Printf("checked %d urls, %d failures\n", len(urls), failures)
	return results
}

The audit_handoff string, decoded:

{
  "verdict": "blocked",
  "headline": "CheckAll spawns unsynchronized goroutines that race on a shared map and counter and can return before they finish, which is the concurrent-map-write crash and the wrong failure count you're seeing.",
  "findings": [
    {
      "id": "GC-02",
      "severity": "high",
      "file": "crawler/crawler.go",
      "line": 41,
      "title": "wg.Add runs inside the goroutine, and it closes over the shared loop variable u",
      "why": "wg.Add(1) runs inside the goroutine, so wg.Wait() on line 50 can see a zero counter before any goroutine has registered and return while checks are still in flight -- the caller then holds a results map and failures count that leftover goroutines are still writing after CheckAll has returned. Because the goroutine reads the loop variable u directly (not a parameter or a copy) and go.mod pins Go 1.21, every goroutine can also see whatever value u has when it runs instead of the URL it was launched for, so some URLs are checked under the wrong key and others never get checked.",
      "edits": 1
    },
    {
      "id": "GC-07",
      "severity": "high",
      "file": "crawler/crawler.go",
      "line": 44,
      "title": "the results map and the failures counter are written from every goroutine with no lock",
      "why": "results[u] = res runs in every one of the per-URL goroutines with no lock: two goroutines writing the same Go map at once is a fatal 'concurrent map writes' runtime error, not just a wrong value, which matches the crash you're seeing. failures++ is a read-modify-write on a shared variable from several goroutines -- a data race that go test -race reports -- so increments are silently lost under load, which is why the printed failure count is always wrong.",
      "edits": 2
    },
    {
      "id": "GC-04",
      "severity": "high",
      "file": "crawler/crawler.go",
      "line": 73,
      "title": "Checker.Checked has a value receiver and copies the mutex it locks",
      "why": "Checked has a value receiver, so c -- including its sync.Mutex -- is copied on every call. Lock/Unlock inside Checked then operate on that copy, not the Checker instance's mutex, so a call to Checked() no longer excludes a concurrent write to checked in check(): go vet's copylocks check flags this, and Checked() can read checked while another goroutine is mid-increment on the real struct.",
      "edits": 1
    },
    {
      "id": "GC-15",
      "severity": "high",
      "file": "crawler/crawler.go",
      "line": 56,
      "title": "the cancel func of context.WithTimeout is discarded",
      "why": "The context and its timer stay referenced until the parent ctx is done (go vet's lostcancel check flags this): with one call per URL and no cancel, this leaks one timer per check() call for as long as the crawl runs, and nothing can free it early once the request completes.",
      "edits": 1
    },
    {
      "id": "GC-19",
      "severity": "low",
      "file": "crawler/crawler.go",
      "line": 40,
      "title": "one goroutine per URL with no limit",
      "why": "CheckAll starts one goroutine per URL with no bound: fine for a short list, but pointed at the whole docs site it opens as many outbound HTTP connections at once as there are URLs, which can exhaust file descriptors or trip the target site's rate limiting. Bound it with errgroup's SetLimit(n) or a fixed-size worker pool reading a jobs channel before running this at scale; the fix is outside these findings' edits since it changes CheckAll's structure rather than one line.",
      "edits": 0
    }
  ],
  "applied": 5,
  "failed": 0
}

The recomputed flags in facts, decoded (the other keys are trimmed for display):

{
  "component": {
    "go_version": "1.21",
    "go_files": 1,
    "primitives": [
      "sync",
      "context",
      "time"
    ],
    "goroutines": 1,
    "handlers": 0
  },
  "files": [
    {
      "path": "go.mod",
      "kind": "gomod",
      "line": 2,
      "end_line": 4
    },
    {
      "path": "crawler/crawler.go",
      "kind": "go",
      "line": 6,
      "end_line": 82
    }
  ],
  ... [trimmed for display: functions, handlers, goroutines, channels, locks, waitgroups, contexts]
  "flags": [
    {
      "uid": "F1",
      "id": "GC-19",
      "sev": "low",
      "file": "crawler/crawler.go",
      "line": 42,
      "text": "one goroutine per item of urls, with no limit",
      "detail": "Fan-out grows with the input: 10,000 items start 10,000 goroutines, each holding a connection or a file. Bound it - errgroup's SetLimit(n), a buffered semaphore channel, or a fixed pool of workers reading a jobs channel."
    },
    {
      "uid": "F2",
      "id": "GC-24",
      "sev": "info",
      "file": "crawler/crawler.go",
      "line": 6,
      "text": "context: 1 goroutine(s), 0 channel(s), 3 lock call(s), 1 derived context(s); go.mod says go 1.21",
      "detail": "Also read: go.mod (gomod). Only what is pasted is checked: a Close, Unlock or Wait that happens in a file you did not paste is outside the check. The page does not run the race detector - go test -race is the ground truth."
    }
  ],
  "verdict_candidate": "ready",
  "notes": []
}

The request body (Idempotency-Key: goconc-desk:fix:713eff9f-1e43:a1):

{
  "task": "fix",
  "code": "// file: go.mod\nmodule example.com/linkcheck\n\ngo 1.21\n// file: crawler/crawler.go\npackage crawler\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"sync\"\n\t\"time\"\n)\n\n// Result is ...",
  "facts": "{\"component\":{\"go_version\":\"1.21\",\"go_files\":1,\"primitives\":[\"sync\",\"context\",\"time\"],\"goroutines\":1,\"handlers\":0},\"files\":[{\"path\":\"go.mod\",\"kind\":\"g...",
  "note": "The link checker sometimes crashes with 'concurrent map writes' and the failure count is always wrong. What do we fix before it runs on the whole docs site?",
  "audit_handoff": "{\"verdict\":\"blocked\",\"headline\":\"CheckAll spawns unsynchronized goroutines that race on a shared map and counter and can return before they finish, wh..."
}

The reply (output.output), whole:

{
  "task": "fix",
  "verdict": "fixed",
  "headline": "With the audit's edits already applied, CheckAll's only remaining problem was unbounded fan-out; it now runs at most 20 checks at a time through a buffered semaphore.",
  "files": [
    {
      "path": "crawler/crawler.go",
      "content": "package crawler\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"sync\"\n\t\"time\"\n)\n\n// Result is what one URL check found.\ntype Result struct {\n\tURL    string\n\tStatus int\n\tErr    error\n}\n\n// Checker checks the links of the Kestrelmoor docs site.\ntype Checker struct {\n\tclient  *http.Client\n\tmu      sync.Mutex\n\tchecked int\n}\n\nfunc NewChecker() *Checker {\n\treturn &Checker{client: &http.Client{Timeout: 10 * time.Second}}\n}\n\n// maxConcurrentChecks bounds how many URLs CheckAll has in flight at once, so\n// a large site list does not open one outbound connection per URL.\nconst maxConcurrentChecks = 20\n\n// CheckAll fetches every URL, at most maxConcurrentChecks at a time, and\n// returns the result of each.\nfunc (c *Checker) CheckAll(ctx context.Context, urls []string) map[string]Result {\n\tresults := make(map[string]Result)\n\tvar mu sync.Mutex\n\tvar wg sync.WaitGroup\n\tfailures := 0\n\tsem := make(chan struct{}, maxConcurrentChecks)\n\tfor _, u := range urls {\n\t\twg.Add(1)\n\t\tsem <- struct{}{}\n\t\tgo func(u string) {\n\t\t\tdefer wg.Done()\n\t\t\tdefer func() { <-sem }()\n\t\t\tres := c.check(ctx, u)\n\t\t\tmu.Lock()\n\t\t\tresults[u] = res\n\t\t\tif res.Err != nil {\n\t\t\t\tfailures++\n\t\t\t}\n\t\t\tmu.Unlock()\n\t\t}(u)\n\t}\n\twg.Wait()\n\tfmt.Printf(\"checked %d urls, %d failures\\n\", len(urls), failures)\n\treturn results\n}\n\nfunc (c *Checker) check(ctx context.Context, url string) Result {\n\tctx, cancel := context.WithTimeout(ctx, 5*time.Second)\n\tdefer cancel()\n\treq, err := http.NewRequestWithContext(ctx, http.MethodHead, url, nil)\n\tif err != nil {\n\t\treturn Result{URL: url, Err: err}\n\t}\n\tresp, err := c.client.Do(req)\n\tif err != nil {\n\t\treturn Result{URL: url, Err: err}\n\t}\n\tresp.Body.Close()\n\tc.mu.Lock()\n\tc.checked++\n\tc.mu.Unlock()\n\treturn Result{URL: url, Status: resp.StatusCode}\n}\n\n// Checked reports how many URLs have been checked so far.\nfunc (c *Checker) Checked() int {\n\tc.mu.Lock()\n\tdefer c.mu.Unlock()\n\treturn c.checked\n}\n"
    }
  ],
  "changes": [
    {
      "file": "crawler/crawler.go",
      "function": "Checker.CheckAll",
      "flags": [
        "F1"
      ],
      "change": "Added a maxConcurrentChecks buffered channel (size 20) as a semaphore: the loop blocks on sem <- struct{}{} before starting each goroutine, and each goroutine releases it with defer func() { <-sem }().",
      "why": "Prevents the crawl from opening one outbound HTTP connection per URL at once when it runs on the whole docs site, which can exhaust file descriptors or trip the target's rate limiting."
    }
  ],
  "verify": [
    "go vet ./...",
    "go test -race ./...",
    "Write a test that calls CheckAll with a urls slice long enough to exceed maxConcurrentChecks against an httptest.Server that counts concurrent in-flight requests (with a mutex or atomic.Int64) and asserts the peak never exceeds maxConcurrentChecks; run it with -race to also confirm results and failures are written safely.",
    "Write a test with a slice of a few dozen distinct URLs served by an httptest.Server and assert len(CheckAll(ctx, urls)) == len(urls) and Checked() == len(urls), so the earlier lost-write and wrong-count symptoms stay fixed."
  ],
  "open": [],
  "questions": [],
  "summary": "The audit's edits (moving wg.Add before the go statement and passing u as a parameter, locking the results map and failures counter, giving Checked() a pointer receiver, and keeping context.WithTimeout's cancel) are already in the code and check out against it - the concurrent-map-write crash and wrong failure count you saw both trace to the now-fixed unlocked map/counter writes in CheckAll. The one thing the audit left open, unbounded fan-out, is what mattered most for running this on the whole docs site, so this reply bounds CheckAll to 20 checks in flight with a semaphore channel; no exported signature changed. go.mod pins Go 1.21 and no errgroup import is present, so the fix uses a plain WaitGroup, mutex and channel rather than golang.org/x/sync/errgroup."
}

The page checks the reply against the code; the API does not

What comes back from the API is the model’s raw text in output.output. The page holds it to account in the browser before showing it (recon.js reconcile, with the facts, the code and the handoff it sent): it applies every edit or returned file to the paste and re-runs its own checks on the result. None of that happens in the API. A direct API caller gets the reply unchecked and must do the applying and re-checking itself.

recon.js also runs in Node, so you can hold a reply to exactly the page’s standard. Save /gokit.js and /recon.js next to this script, pass the file you built the body from (build-check.js or handoff.js output), the reply text and the lane; it prints every statement, the count, and the paste with every edit (or file) put back:

// recheck.js -- run: node recheck.js crawler-check.json crawler-reply.txt audit > crawler-edited.txt   (gokit.js, recon.js next to it)
const fs = require("fs");
const Recon = require("./recon.js");                                            // loads ./gokit.js itself
const sent = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));             // code, facts (and audit_handoff) you sent
const lane = process.argv[4] || "audit";
const res = Recon.normalize(Recon.parseResult(fs.readFileSync(process.argv[3], "utf8")), lane);  // output.output
const r = Recon.reconcile(res, { facts: sent.facts, code: sent.code, handoff: sent.audit_handoff || null });
r.notes.forEach((n) => console.error(n.kind + ": " + n.text));
console.error(r.disagreements + " disagreement(s) in " + r.statements + " statements checked");
if (r.after) (r.after.flags || []).forEach((f) => console.error("still raised: " + f.id + " " + f.sev + " " + f.file + ":" + f.line + " " + f.text));
if (r.after && r.after.text) console.log(r.after.text.replace(/\n$/, ""));      // the paste with every edit (or file) put back

For the crawler audit reply it reports:

ok: The areas table rates lifecycle, shared state, channels and cancellation once each.
ok: Every one of the 5 findings names a pasted file, a line inside it and evidence copied from it.
ok: The verdict (blocked) fits the worst finding.
ok: Every one of the page's 7 flags is answered once.
ok: All 5 edits applied in order, each before text found exactly once.
ok: Every confirmed flag a finding carries is gone once all edits are applied.
ok: Every function of the paste is still there after the edits.
ok: The reply repeats no credential from the paste.
0 disagreement(s) in 8 statements checked
still raised: GC-19 low crawler/crawler.go:42 one goroutine per item of urls, with no limit

for the pipeline fix reply (node recheck.js pipeline-check.json pipeline-reply.txt fix):

ok: 1 file returned whole, each with its package clause.
ok: With the fixed files in place the free check raises no high or medium flag beyond the ones left open (0 low).
info: F4 left open by the reply.
ok: Every one of the 3 functions is still there.
ok: Every one of the 3 changes names a returned file, a function in the fixed code and real page flags.
ok: The verdict (partial) fits the open list.
ok: The reply repeats no credential from the paste.
0 disagreement(s) in 6 statements checked
still raised: GC-14 medium ingest/pipeline.go:75 the select has an empty default inside a loop, so the loop spins

and for the crawler handoff fix (node recheck.js crawler-fix-check.json crawler-fix-reply.txt fix):

ok: 1 file returned whole, each with its package clause.
ok: With the fixed files in place the free check raises no high or medium flag (0 low).
ok: Every one of the 4 functions is still there.
ok: Every one of the 1 changes names a returned file, a function in the fixed code and real page flags.
ok: The verdict (fixed) fits the open list.
info: This run carried the audit (5 findings, verdict blocked) with its edits already in the code.
ok: The reply repeats no credential from the paste.
0 disagreement(s) in 6 statements checked

The page does not compile the result and is not the race detector. After that, run go vet ./... and go test -race ./... on the edited code, plus the tests the reply’s plan or verify names, before you ship anything.

Step 1 — a tiny client helper

One function per language that does what sdk.js does for every call: JSON in, bearer token if you have one, data out on a 2xx, an error carrying status, error.code and error.message otherwise. The later steps assume this helper is in the same file (for Go, Java and C#, the later snippets go inside main / the top-level statements; Java’s makeInput from step 4 goes in the class).

BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="${SKILLSAFE_TOKEN:-YOUR_TOKEN}"   # step 2

# api METHOD PATH [extra curl args...] -- prints the whole envelope; pipe to jq
api() {
  method="$1"; path="$2"; shift 2
  curl -sS -X "$method" "$BASE$path" \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer $TOKEN" "$@"
}
# success: jq '.data'      failure: jq '.error | {code, message}'

Step 2 — a token

Two kinds work:

# personal token (needed for a run): copy it from /tokens.html, then
#   export SKILLSAFE_TOKEN=...        and skip the rest of this block
# guest token (enough for /me and /estimate): no Authorization header on this one call
TOKEN=$(curl -sS -X POST "$BASE/guest" \
  -H "Content-Type: application/json" \
  -d '{"slug":"goconc-desk"}' | jq -r '.data.token')
echo "guest token: ${TOKEN:0:12}..."

Step 3 — GET /me

Who the token belongs to and what it can spend. sdk.js documents the shape as {subject_type, subject_id, credits, profile?}. The page treats the caller as signed in only when subject_type is "user", shows credits as the balance (1 credit = $0.0001, so it divides by 10 000 for the dollar figure), and compares it with the estimate in step 4 before it lets you run.

api GET /me | jq '.data // .error'

Step 4 — build the input, then POST /estimate

First a small makeInput that does what buildInput does: task, then code as the paste the page would send, facts as a compact JSON string, the note when it is not blank, and — for the fix lane only — the audit_handoff string when the file carries one. It reads the file build-check.js (or handoff.js) printed. The samples build the audit input of the crawler example from crawler-check.json; step 6 reuses makeInput for the pipeline (fix) example. Then price it. The body of /estimate is the same input you will send to /run; no job is created and nothing is charged — /estimate is free. From the response the page reads:

FieldMeaning
hold_creditsA reservation, not a price: the worst case the platform holds while the run is in flight. The page shows it as “reserves N credits ($...) for the audit run” and adds that you are charged only for what the run actually uses, usually far less. What you pay is the job’s charged_credits after settlement (step 5). A balance below the hold is allowed, but the page warns that the reply may be cut short (truncated: true).
min_creditsThe smallest balance that can start a run. Below it the page disables the run button and names the shortfall, because the call would be a 402.
sponsor_enabledWhen true, a caller who is not signed in may run; otherwise the page asks them to sign in.
warningsInput warnings. They do not refuse the call; the page shows them next to the price (“input warnings: ...”), so check them before you run.

The page re-prices whenever the paste, the note or the lane change. The amounts grow with the size of code and facts, so read them from the call rather than hard-coding them.

# crawler-check.json: printed by build-check.js (see "The facts string").
# (.facts | tojson) turns the facts into ONE compact string, as JSON.stringify does.
BODY=$(jq -c --arg note "The link checker sometimes crashes with 'concurrent map writes' and the failure count is always wrong. What do we fix before it runs on the whole docs site?" \
  '{task: "audit", code, facts: (.facts | tojson), note: $note}' crawler-check.json)
# No page check at hand? facts: "{}" is accepted as a string, but the reply then has no page flags to answer.
# Fix lane after an audit: read handoff.js's output instead and add its string:
#   '{task: "fix", code, facts: (.facts | tojson), note: $note} + (if .audit_handoff then {audit_handoff} else {} end)'

api POST /estimate -d "$BODY" \
  | jq '.error // (.data | {hold_credits, min_credits, sponsor_enabled, warnings})'

Step 5 — POST /run, then poll the job (audit)

Send the same input to /run with an Idempotency-Key header, using a personal token. Its data carries a job_id. Poll GET /jobs/{job_id} the way sdk.js waitForJob does: every second until status is succeeded or failed, giving up on the client side after 180 seconds. On the finished job, read:

Idempotency key. Resending a request with the same key must not start a second paid run, so derive the key from the input, not from the clock — and include the lane, so an audit and a fix run of the same paste never share a key. The page uses goconc-desk:<lane>:<inputHash>:a<attempt> (app.js run), where <lane> is audit or fix, <inputHash> is a 32-bit djb2 hash of JSON.stringify(input) in hex, a dash, and the string’s length in hex (app.js inputHash, over the input without retry_note), and <attempt> is 1 for a normal run and 2 for the automatic reformat retry (which also adds retry_note). For the three worked examples that is goconc-desk:audit:fb06d86d-1942:a1 (crawler audit), goconc-desk:fix:fed85a1a-167f:a1 (pipeline fix) and goconc-desk:fix:713eff9f-1e43:a1 (the crawler handoff fix). The format is the page’s convention, not a requirement: any stable, unique string does. The samples below use the lane and a SHA-256 prefix.

The page itself uses /run-stream (step 6), not /run; both take the same body and header.

KEY="goconc-desk:audit:$(printf '%s' "$BODY" | shasum -a 256 | cut -c1-16):a1"

JOB=$(api POST /run -H "Idempotency-Key: $KEY" -d "$BODY" | jq -r '.data.job_id')

for i in $(seq 180); do                       # 1 s interval, 180 s client-side limit
  J=$(api GET "/jobs/$JOB")
  S=$(printf '%s' "$J" | jq -r '.data.status')
  if [ "$S" = "succeeded" ] || [ "$S" = "failed" ]; then break; fi
  sleep 1
done

printf '%s' "$J" | jq '.data | {status, truncated, charged_credits, error}'
printf '%s' "$J" | jq -r '.data.output.output' \
  | jq '{verdict, headline, areas: [(.areas // [])[] | {area, status}],
         findings: [(.findings // [])[] | {id, file, line, severity, edits: ((.edits // []) | length)}],
         flag_answers: [(.flag_answers // [])[] | {flag, status}]}'

Step 6 — POST /run-stream (Server-Sent Events, fix)

The alternative to step 5, and what the page uses: same kind of body, same Idempotency-Key header, one request. Run one or the other for a given input, not both. To show the second lane, the samples build the fix input of the pipeline example (with its own key) and stream it; to stream the audit input instead, pass the step 4 input and key. The response is text/event-stream. sdk.js splits it into frames on a blank line, takes the event name from an event: line (default message) and concatenates the data: lines, skips frames with no data or with data that is not JSON, and handles these events:

EventDataWhat the SDK does
delta{text}Passes text to onDelta.
jobjob infoPasses it to onJob (the page uses it to advance its progress display).
donethe final payload: {job_id, status, charged_credits, output}Keeps it as the result.
pendinga payloadTreated exactly like done. If it carries a job_id and no output, poll that job as in step 5.
error{message, code, job_id}After the stream ends, raises an error with those fields.

If the response is not text/event-stream, it is an ordinary JSON envelope: the SDK returns its data (an idempotent replay returns this way) or raises the error on a non-2xx status. Handle both shapes.

From the final payload the page reads output.output (falling back to the concatenated delta text), truncated and charged_credits. Browsers receive progress ticks rather than deltas from this endpoint, so the page relies on the final payload; if the stream fails part-way, it tries to recover a partial reply from whatever delta text arrived. The samples print the verdict and headline, each returned file’s path and size, each change (function, flags and change), each verification step, each open item and each question. To put the files into the paste and re-check them the way the page’s “Put every file into the paste” does, keep the reply text and run recheck.js with the lane fix. To fix code you audited, read handoff.js’s output instead of pipeline-check.json: makeInput then carries its audit_handoff.

# the fix worked example: pipeline-check.json from build-check.js
# (for audited code, read handoff.js's output instead; the + (...) adds its audit_handoff)
FBODY=$(jq -c --arg note "Parse hangs in production and sometimes panics with 'close of closed channel'; Lookup leaks goroutines when the partner API is slow. Fix those two, no new dependencies. Leave Drain exactly as it is for now - the status team owns it and is rewriting it next sprint." \
  '{task: "fix", code, facts: (.facts | tojson), note: $note} + (if .audit_handoff then {audit_handoff} else {} end)' \
  pipeline-check.json)
FKEY="goconc-desk:fix:$(printf '%s' "$FBODY" | shasum -a 256 | cut -c1-16):a1"

# -N turns off buffering; the whole stream lands in $STREAM
STREAM=$(api POST /run-stream -N -H "Idempotency-Key: $FKEY" -d "$FBODY")

# frames look like:
#   event: job
#   data: {...}
#
#   event: done
#   data: {"job_id":"...","status":"...","charged_credits":...,"output":{"output":"{\"task\":\"fix\",...}"}}

# the final reply text, from the done (or pending) frame:
REPLY=$(printf '%s\n' "$STREAM" \
  | awk '/^event:/{ev=$2} /^data:/ && (ev=="done" || ev=="pending"){sub(/^data: */,""); print}' \
  | jq -r '.output.output')
# an idempotent replay answers with a plain JSON envelope instead: REPLY=$(printf '%s' "$STREAM" | jq -r '.data.output.output')
printf '%s' "$REPLY" | jq '{verdict, headline,
  files: [(.files // [])[] | {path, characters: (.content | length)}],
  changes: [(.changes // [])[] | {function, flags, change}], verify, open, questions}'

What the page does that you now do

Or skip all of this and use GoConc Desk in the browser: the check, the flags, the goroutine, channel, lock and context inventories, putting an edit or the fixed files back into the paste, and the Markdown and CSV exports are free.