Following the discussion at #7870: `-fdump-passes` reports passes
enabled dynamically (e.g. loop2_unroll via `#pragma GCC unroll`) as OFF,
so they were missing from the pass dropdown although they were active.
Fixed by building the pass list from the dump files GCC actually wrote
instead.
While reworking this:
- Read all passes into a `passDumps` map shipped with the result, so
switching passes doesn't trigger a compile.
- Trim each dump to functions defined in the user's source, dropping
header/library functions; strip forced -lineno annotations from GIMPLE
(tree/IPA) dumps when Line Numbers is off, leaving RTL untouched.
- Exclude passes with empty output
- Add a GccDumpOutput type, backend unit tests and a cypress test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
## What
Adds
[OpenSearch](https://developer.mozilla.org/en-US/docs/Web/XML/Guides/OpenSearch)
support so browsers can register a `ce` keyword. Once registered, typing
`ce <shortlink>` in the address bar redirects to `/z/<shortlink>`.
## How
- **`views/opensearch.pug`** — an OpenSearch 1.1 description document.
- **`/search.xml` route** (`lib/app/server-config.ts`) — mirrors the
existing `/sitemap.xml` route; serves the doc with `Content-Type:
application/opensearchdescription+xml`.
- **`views/meta.pug`** — advertises it via `<link rel="search">` on the
main page (omitted on embed pages, where OpenSearch discovery doesn't
apply).
The search URL template (`…/z/{searchTerms}`) is built from the request
(`{protocol}://{host}{httpRoot}`, the same pattern as
`lib/storage/base.ts`), and the favicon uses the env-resolved
`staticRoot`/`faviconFilename`, so it's correct across
prod/beta/staging/local.
## Activation note
This is opt-in *discovery*, not automatic activation — browsers register
the engine after visiting the site, and the user typically activates the
`ce` keyword once (Chrome: Settings → Search engines → Site search;
Firefox surfaces it in the address bar). Chrome's auto-discovery is also
gated on a secure context, so this is best validated on an HTTPS
environment (e.g. staging/beta) rather than a plain-HTTP dev box.
## Testing
Verified against a local instance:
- `GET /search.xml` → `200`, correct content-type, well-formed XML,
`{searchTerms}` placeholder preserved, `<Url template>` absolute and
host-derived.
- `<link rel="search">` present on `/`, absent on `/e`.
- favicon URL resolves.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: mattgodbolt-molty <mattgodbolt-molty@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This adds support for
[RazorForge](https://github.com/dj-lumiere/razorforge-suflae),
a precision-focused, ahead-of-time compiled language that lowers to LLVM
IR. It follows
`docs/AddingALanguage.md`.
### What's included
- `lib/languages.ts` — language definition (`.rf`, Monaco mode
`razorforge`, disassembly `llvm-ir`)
- `types/languages.interfaces.ts` — `razorforge` language key
- `lib/compilers/razorforge.ts` + `lib/compilers/_all.ts` — compiler
driver
- `static/modes/razorforge-mode.ts` + `static/modes/_all.ts` — Monarch
syntax highlighting
- `etc/config/razorforge.{defaults,amazon}.properties` — compiler config
- `examples/razorforge/default.rf` — default example
- `.github/labeler.yml` — `lang-razorforge` label
### Compiler behavior
RazorForge's CLI takes verbs + positional arguments only (all build
config lives in
`razorforge.toml`, not flags). The driver invokes the `build` verb,
which runs semantic
analysis and code generation and writes LLVM IR next to the source
(`example.rf` →
`example.ll`) without invoking `opt`/`clang`. The primary output is the
emitted LLVM IR,
so `supportsIrView` is on and `supportsExecute` is off for now.
### Notes
- Install recipe: compiler-explorer/infra#2177
- No logo yet (`logoFilename: null`); happy to add one in a follow-up.
- Verified locally: `make`, `biome check`, `tsc`, and `vitest related`
all pass; the example
compiles to valid LLVM IR with the released `v0.0.3-alpha` build.
Follow-up to #8779 (which fixed#8601, `builtin.sourcePath` being
ignored). Two related changes:
### 1. Construct the builtin source explicitly at startup (restore
fail-fast)
#8779 made the builtin "Examples" source read its config lazily, on the
first `list()`/`load()`. That fixed correctness but moved the failure
mode: a misconfigured `sourcePath` (missing/unreadable dir) no longer
fails at startup. The server boots, passes healthchecks, takes traffic,
and then throws on the first request that opens Examples.
This restores fail-fast by constructing the source explicitly once
configuration is loaded:
- Replace the `builtin` module-level singleton with a `BuiltinSource`
class whose constructor scans the examples directory, plus a
`createBuiltinSource()` factory that reads `builtin.sourcePath`.
- `lib/sources/index.ts` exposes `createSources()` instead of a
top-level `sources` array (a top-level array would re-run config reads
at import time, reintroducing #8601).
- `initialiseApplication` constructs the sources after config load and
passes the `Source[]` to `ClientOptionsHandler` and
`setupControllersAndHandlers` (both already accept injected sources).
A bad `sourcePath` now throws during startup, before the instance
reports healthy. The lazy fix's correctness (config read after
`initialize()`) is preserved.
### 2. Single source of truth for the examples path
`GolangParser` independently re-read `('builtin', 'sourcePath',
'./examples/')` to locate `go/default.go`, duplicating where the
examples directory is defined. Export `getExamplesRoot()` from
`lib/sources/builtin.ts` and use it in both `createBuiltinSource()` and
`GolangParser`, so the config key and default live in one place.
Behaviour is unchanged (same value resolved); it removes the drift risk
if the key or default ever changes.
### Tests
Tests construct `BuiltinSource` directly with a fixture directory (no
import-order/`resetModules` dance), cover the unknown-example path,
assert fail-fast on a non-existent dir, and verify
`createBuiltinSource()` reads the configured path.
- `npx vitest --run test/sources/builtin-tests.ts
test/compilers/argument-parsers-tests.ts`
- `npm run ts-check`
- `npm run lint-check`
---------
Co-authored-by: mattgodbolt-molty <mattgodbolt-molty@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Summary
- defer scanning builtin examples until `list()` or `load()` is called
- refresh the cached examples if `builtin.sourcePath` changes
- add a regression test that imports the builtin source provider before
properties are initialized, then verifies a custom `sourcePath` is used
Closes#8601.
## Tests
- `npm run test -- --run test/sources/builtin-tests.ts --reporter=dot`
- `npm run test-min -- --run test/sources/builtin-tests.ts
--reporter=dot`
- `npm run ts-check`
- `npx biome check lib/sources/builtin.ts test/sources/builtin-tests.ts
--write`
- `npm run lint-check -- lib/sources/builtin.ts
test/sources/builtin-tests.ts`
- `git diff --check`
Co-authored-by: Deepak kudi <deepakkudi23@adsl-172-10-9-116.dsl.sndg02.sbcglobal.net>
clang-cl doesn't write mapfiles like MSVC, so it doesn't support `/Fm`
and it used to report a warning:
```
clang-cl: warning: argument unused during compilation: '/FmC:\Windows\TEMP\compiler-explorer-compiler6Pv4en\output.s.exe.map' [-Wunused-command-line-argument]
```
To still get the function labels, we should use `llvm-objdump`. However,
that doesn't read the generated debug info file right now. I've opened
https://github.com/llvm/llvm-project/pull/201150 to fix this.
When the user specified `/link` in the options or enabled "Link to
binary", clang-cl wouldn't generate IR or an AST.
Two changes fix this:
- Strip `/link` and all options after it. These are unused for IR and
AST generation anyway.
- Always specify `-c` for IR generation. For the AST, `-fsyntax-only` is
already specified in the base class.
The `LLVMIRDemangler` derived from `BaseDemangler`. It called the
demangler executable as-if it was `c++filt`. This would fail if the
demangler was `(llvm-)undname` (demangler for Microsoft names), because
it has a different output.
This PR refactors `LLVMIRDemangler` to take a demangler instance that it
calls into. `BaseDemangler` was extended to allow demangling a fixed set
of symbols and to skip applying the names. Both are needed for the IR
demangler. This way, the demangler instance can handle the details of
parsing the stdout and `LLVMIRDemangler` can provide the names and apply
the transforms.
Fixes#8816.
- `setupTempDir()` exports the configured dir as `TMPDIR`, `TMP` *and*
`TEMP`. POSIX `os.tmpdir()` consults `TMPDIR` first, so the old TMP-only
export meant an inherited `TMPDIR` silently defeated `--tmp-dir`
(verified empirically; prod was protected only by `sudo env_reset` in
start.sh). Setting all three also covers native Windows (`TEMP` > `TMP`
there) and WSL, and means spawned tools reading any of the variables
agree.
- Restores the `os.tmpdir() !== tmpDir → throw` sanity check from
613d7f688 (#6052), lost in the #7681 split refactor.
- The startup log now prints `os.tmpdir()` — the value that actually
matters — instead of `TEMP || TMP`, which printed `undefined` on a
default Linux run.
- The `lib/temp.ts` exit hook was `process.on('exit', async ...)`: exit
handlers can't await, so it never removed anything. Replaced with a
synchronous `cleanupSync()` (tested).
- Test hygiene fix that the work surfaced: the temp-dir tests restored
the environment by reassigning `process.env` wholesale; a replaced
`process.env` is a plain object whose writes never reach the real
environment, while `os.tmpdir()` reads the real environ via `safeGetenv`
— so the suite silently leaked env state across tests (and into any test
running later in the same worker). Now saves/restores the individual
variables.
The WSL `%TEMP%`-discovery-failure path (`wsl-vc.ts` parsing garbage on
fallback, #8816 item 4) is deliberately untouched — Windows-specific and
unverifiable here.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: mattgodbolt-molty <mattgodbolt-molty@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The last loose end from the incident issue —
`ce_compilation_queue_completed_total` incremented in a `finally` that
ran as soon as the job *returned its promise*, so it counted dequeues,
in lockstep with `ce_compilation_queue_dequeued_total`, and
`status().running` was almost always 0.
Completion is now counted when the job settles (fulfilled or rejected),
via a settlement callback rather than by awaiting in the wrapper. The
non-awaiting detail matters: my first attempt awaited `job()` so the
`finally` ran at settlement — and the existing "times out a job that
never settles" test immediately caught that this keeps `_running`
populated forever for a wedged job, reintroducing the exact
`busy`-forever wedge #8813 fixed. (A nice demonstration of that test
paying for itself.) With the callback approach, a never-settling job
correctly never counts as completed, so `dequeued − completed` now
exposes wedged/in-flight jobs — which would have made the original
incident visible directly in Grafana.
New test pins the semantics: counter unchanged while a job is running,
+1 once it settles.
Per discussion: no temp-dir sweeps of any kind (instances are replaced,
never restarted; and multiple CE processes may share a machine), so the
orphaned-dirs observation in #8811 is closed as won't-fix.
Closes#8811.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: mattgodbolt-molty <mattgodbolt-molty@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Fixes#8817. Kept deliberately simple per discussion — the conan server
is CE's own infrastructure, so these are hygiene bounds, not attack
mitigations:
- A generous fixed 2GiB cap on the total declared size of extracted
files (tar-stream enforces entry bodies match their headers, so summing
`header.size` bounds bytes written). Real packages are tens to a few
hundred MiB; hitting this means a packaging error or a corrupt/bombed
archive, and the extraction rejects cleanly through the existing error
path.
- The package URL conan returns must be http(s). No redirect
restrictions (conan may legitimately hand out redirecting/presigned
URLs), no config plumbing.
Both paths tested (cap exercised by lowering the limit on the instance
under test; scheme via a `file://` URL).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: mattgodbolt-molty <mattgodbolt-molty@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Part of #8811 (production disk-full incident).
During the incident `/healthcheck` stayed green for hours while every
compilation failed with ENOSPC: the empty-job queue check passed (one of
two queue slots still worked), and the EFS health-file read doesn't
touch the root filesystem — so the load balancer never replaced the
instance.
This adds a free-space check on the filesystem where compilation temp
dirs are actually created: `lib/temp.ts` gains `getTempRoot()`, used
both by `temp.mkdir()` and the healthcheck, so the two can't drift
apart. (That resolves via `os.tmpdir()`; `--tmp-dir` flows into it as
`$TMP` via `setupTempDir()` — `/nosym/tmp` in prod.) Below the threshold
the healthcheck returns 500 and the load balancer replaces the instance
*before* hard failure. A `statfs` failure is itself treated as
unhealthy.
Configuration: `healthCheckMinFreeSpaceMiB` — required constructor
argument (no code default, per review); `amazon.properties` sets 2048
(at the incident's ~300MB/min fill rate that gives the LB several
minutes to act); the ceProps fallback is 100 for unconfigured/local
instances; 0 disables the check.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: mattgodbolt-molty <mattgodbolt-molty@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Part of #8811 (production disk-full incident).
## The incident bug
If a conan library package download's connection died mid-transfer,
`downloadAndExtractPackage()`:
- threw an **uncaught exception** (`TypeError: terminated`): the error
was emitted on the `Readable.from(res.body)` wrapper, which had no
`'error'` listener — `.pipe()` neither attaches nor forwards one;
- **never settled its promise**: gunzip/tar saw neither an error nor an
end, so neither `resolve` nor `reject` was ever called. This wedged a
compilation queue slot forever, which permanently disabled temp dir
cleanup (it only runs when the queue is idle) and filled the disk on
prod.
The download path is rewritten around `stream.pipeline()`, which
propagates errors through every stage and guarantees settlement.
Per-entry file writes also go through `pipeline()`, so write errors
propagate (via `extract.destroy`) instead of being silently dropped, and
`next()` fires after the file is fully flushed rather than on stream
`'end'`.
## Extraction hardening (from review + an adversarial security pass)
- **Zip-slip guard anchored at the per-library extraction root**
(`downloadPath/<libId>`; plain `downloadPath` for `extractAllToRoot`): a
package can no longer write over a sibling library's files or the
compilation's own. The check is `path.relative`-based — immune to prefix
collisions (`/tmp/pkg` vs `/tmp/pkg-evil`) and to directories merely
*named* with leading dots.
- **Only regular-file entries are ever written**: directories, symlinks,
hardlinks and other types are drained and skipped, including malformed
entries (e.g. a directory claiming non-zero size) whose body could
otherwise land on disk as a file. CE never creates links of any kind;
prod additionally runs under the `/nosym/tmp` nosymfollow mount.
- **Zero-length files extract correctly** (the pre-pipeline code created
them as a side effect of an early `createWriteStream` — with a leaked
fd; the first pipeline version dropped them entirely).
- **Logs preserve stacks** (error objects passed to winston, not
interpolated) and the archive-controlled entry name is JSON-stringified.
Deliberately out of scope (filed as #8817): decompressed-size caps and
`packageUrl` scheme/redirect validation — defense-in-depth against our
own conan server, not blockers. See the review-convergence comment below
for vectors evaluated and rejected with rationale.
## Tests
`test/buildenvsetup-ceconan-tests.ts` (new), against a local HTTP
server:
- **Severed mid-stream download rejects rather than hanging** — against
the pre-fix code this reproduces the production failure exactly
(unhandled `TypeError: terminated` + timeout).
- Happy-path extraction including a zero-length file.
- 404 → rejection.
- Zip-slip: full escape and sibling-library escape are skipped; a
`..`-named directory inside the library root still extracts.
- A malformed sized-directory entry settles (no wedged promise) and
writes nothing.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: mattgodbolt-molty <mattgodbolt-molty@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Part of #8811 (production disk-full incident). Companion to #8812 — this
is the systemic backstop so no future never-settling job can wedge the
system.
Two changes:
**Queue timeout was dead config.** `enqueue()` passed `timeout:
undefined` to p-queue's `add()`; p-queue spreads per-call options over
its defaults, so this overrode and *disabled* the queue-wide timeout
configured from `compilationEnvTimeoutMs` (default 300s). A job whose
promise never settled therefore occupied a queue slot forever — and
since temp dir cleanup only runs when the queue is fully idle, one
wedged slot permanently disabled cleanup and filled the disk. Removing
the override makes the timeout effective: in p-queue v9 a timed-out task
rejects with `TimeoutError`, freeing the slot. (The timeout doesn't kill
underlying work — that remains the exec layer's job — but the system
makes progress again.)
**Uncaught exceptions now actually stop the process.** The handler set
`process.exitCode = 1` assuming the app would "exit naturally", but a
process with live server listeners never does: during the incident the
instance limped on half-dead for hours, passing healthchecks while every
compilation failed. Now it exits after a 1s delay (letting winston flush
its transports), and the load balancer replaces the instance.
The new queue test fails against the previous code: a never-settling job
is never rejected and `status().busy` stays true forever. Also adds
basic enqueue/nested-enqueue coverage.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: mattgodbolt-molty <mattgodbolt-molty@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Automated fix by @leftibot.
### What changed
> Fix#8695: add Lua as a supported language
> Add support for Lua via the reference PUC-Rio interpreter. Disassembly
is
> produced by `luac -l -l -p`, which writes a verbose bytecode listing
to
> stdout that the LuaCompiler captures and writes to the output file.
The
> class exposes overridable hooks (`resolveLuacExe`,
`getDisassemblyArgs`)
> so alternative implementations such as LuaJIT can plug in a different
> bytecode dumper without rewriting the compiler. Production config
ships
> five Lua releases (5.1.5, 5.2.4, 5.3.6, 5.4.7, 5.5.0) covering the
> actively used minor versions.
### Files
```
etc/config/lua.amazon.properties | 27 ++++++
etc/config/lua.defaults.properties | 5 +
examples/lua/default.lua | 5 +
lib/compilers/_all.ts | 1 +
lib/compilers/lua.ts | 182 +++++++++++++++++++++++++++++++++++++
lib/languages.ts | 11 +++
test/lua-tests.ts | 138 ++++++++++++++++++++++++++++
types/languages.interfaces.ts | 1 +
8 files changed, 370 insertions(+)
```
Closes#8695
_Triggered by @lefticus._
---------
Co-authored-by: leftibot <leftibot@users.noreply.github.com>
Co-authored-by: Matt Godbolt <matt@godbolt.org>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Summary
Fixes#8709.
This tightens the generic `file:line` parser so c2rust AST dump lines
like `CTypeId(42): Located {` are left as plain output instead of being
interpreted as source diagnostics.
## Test plan
- `npm test -- test/utils-tests.ts`
- `npm run lint-check -- lib/utils.ts test/utils-tests.ts`
- `npm run ts-check:tests`
- `npm run ts-check:backend`
- `git diff --check`
---------
Co-authored-by: cyphercodes <cyphercodes@users.noreply.github.com>
Fixes
https://github.com/compiler-explorer/compiler-explorer/issues/5634.
Adds the Lean 4 language and compiler.
> Lean is an open-source programming language and proof assistant that
enables correct, maintainable, and formally verified code
Since it was first requested in
https://github.com/compiler-explorer/compiler-explorer/issues/5634, it
has become increasingly relevant due to interest in AI-assisted theorem
proving, formalized mathematics, and verified software.
---
Lean compiles in two steps: first to C source code using the `lean`
executable, and then to assembly using the `leanc` compiler distributed
with Lean (I believe it's Clang).
The PR also includes a pane to visualize the emitted C source code that
was modelled after the panes for Rust and Haskell IRs and after the C
preprocessor pane for clang-format integration. Lean outputs mostly
unindented C code, so I enabled the formatter by default.
See companion infrastructure PR at
https://github.com/compiler-explorer/infra/pull/2130.
<img width="3024" height="1720" alt="image"
src="https://github.com/user-attachments/assets/5e7a1e91-e748-4599-8190-d3cb09fca503"
/>
---------
Co-authored-by: Matt Godbolt <matt@godbolt.org>
Since .NET 11, R2R file is no longer limited to PE file.
For example, it can now print `Emitting R2R Wasm file`, which bypassed
our asmline filter.
Loosen the text matching a bit by only recognizing `Emitting R2R` with a
tailing space.
Optimize the .NET asm parser.
- Cap generic arity at `1024 * 1024` to avoid unbounded generic
instantiations
- Cache sorted source-mapping offsets per method and use binary search
for offset lookup so that we don't need to sort every time
- Avoid allocating an intermediate array for `INLRT` offset matches
- Use `Set` for labels to allow faster check
Resolves#8701.
The `s3` storage backend requires an `express.Request` object to get the
request’s IP address, but the code passed `{} as express.Request`, which
led to an exception.
As a result, the fallback implementation was always used in prod, which
generates a link of the form `godbolt.org/#base64_encoded_clientstate`,
but `/#...` expects a `rison`-encoded string.
This PR implements the source mapping for .NET compilers (C#, F#, VB.NET
and IL), supports CoreCLR, Crossgen2 and NativeAOT compilers.
- Add a parser that walks ECMA-335 metadata and Portable PDB sequence
points to build IL-offset-to-source mappings for .NET methods
- Load PDBs emitted by the .NET compiler after building the dll, and
then save the source mapping in the result
- Extend the .NET asm parser to match disassembly method signatures,
including generic types, generic methods, and inline root offsets, so
emitted asm lines inherit the correct source locations
- Use debug info emitted by the JIT `INLRT @ 0x##` as anchors to match
the offset
Showcase:


## Summary
Anthropic's [connector review
criteria](https://claude.com/docs/connectors/building/review-criteria)
and [submission
requirements](https://claude.com/docs/connectors/building/submission)
call out two things our `/mcp` endpoint is missing today:
- every tool needs a `title` plus `readOnlyHint` / `destructiveHint`
annotation
- a public docs page covering setup and usage must exist by publish date
This PR adds both:
- **Tool annotations** on all 7 MCP tools (`compile`, `list_compilers`,
`list_languages`, `list_libraries`, `lookup_asm_instruction`,
`generate_short_url`, `get_shortlink_info`). All read-only tools get
`readOnlyHint: true`. `generate_short_url` is marked `readOnlyHint:
false` + `destructiveHint: false` + `idempotentHint: true` (it's
additive and the storage layer dedupes by config hash). Every tool gets
`openWorldHint: false` since none reach out to third-party services.
- **`docs/MCP.md`** describing the endpoint URL, transport, tool
catalogue, and a Claude Code setup line.
A judgement call worth flagging: `compile` is annotated `readOnlyHint:
true` even though `execute=true` runs user code. The CE service is
stateless from the connector's point of view and sandbox effects don't
escape the call, so the hint matches the spirit of the annotation, but
we may want to revisit if Anthropic pushes back during review.
Two known gaps not addressed here, that I'd like to discuss separately
before submission:
- **Origin-header validation** — required by the submission doc as
DNS-rebinding mitigation. The threat model mostly applies to
localhost-bound desktop servers; we're a public HTTPS service with no
auth, so the public REST API's `Allow-Origin: *` posture is consistent.
Suggest asking Anthropic to confirm exemption, or add an allowlist with
a missing-Origin pass-through (Claude Code currently sends none).
- **Submission form prep** — name/tagline/description/screenshots/logo,
and a pass through MCP Inspector. Operational, not code.
## Test plan
- [x] `npm run ts-check`
- [x] `npm run lint`
- [x] `npm run test -- --run mcp` (78 tests pass)
- [x] pre-commit hook runs clean
- [ ] manual smoke test from a fresh Claude Code MCP install (`claude
mcp add --transport http compiler-explorer https://godbolt.org/mcp` once
deployed)
- [ ] confirm tool annotations show up correctly in MCP Inspector
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Summary
Three related fixes prompted by driving the deployed MCP endpoint after
#8644 / #8685 merged:
1. **`list_libraries` match also searches version strings.** The natural
LLM query "find Boost 1.88" returned zero results —
`list_libraries({language:"c++", match:"boost 1.88"})` only matched
against `{id, name}`, so the version token never matched anything.
2. **HPPA + Go cross-compiler `instructionSet` retags.** Discovered via
`list_compilers(match="gcc 16", instructionSet="amd64")` returning
*only* `hppag1610` (HPPA cross-compiler) and missing plain `g161`
(x86-64 GCC). Two layered causes — HPPA had no entry in
`InstructionSetsList`, so the heuristic in `lib/instructionsets.ts`
defaulted HPPA cross-compilers to `amd64`; and 12 Go cross-compiler
groups (`386gl`, `arm32gl`, `arm64gl`, `mips*gl`, `ppc64*gl`, `riscvgl`,
`s390xgl`, `wasmgl`) had never been tagged at all.
3. **Defensive bucketing in MCP `pickLatest`.** Belt-and-braces fix:
bucket key is now `(lang, instructionSet, group)` so different compiler
families that happen to share an arch tag can't fight for the same
`latestPerMajor` slot.
## Changes
- `lib/mcp/tools/libraries.ts` — extend `applyMatch` haystack to include
`versions[].version` (human form, e.g. `"1.88.0"`) and `versions[].id`
(e.g. `"188"`).
- `lib/mcp/tools/compilers.ts` — `pickLatest` bucket key now includes
`c.group`.
- `types/instructionsets.ts` + `lib/instructionsets.ts` — new `'hppa'`
entry.
-
`etc/config/{ada,c++,c,d,fortran,gimple,objc++,objc}.amazon.properties`
—
`group.<gnathppa|gcchppa|cgcchppa|gdchppa|gimplehppa|objcppgcchppa|objchppa>.instructionSet=hppa`.
- `etc/config/go.amazon.properties` — explicit `instructionSet=` for all
13 Go arch groups (12 cross + amd64gl for symmetry).
- `test/mcp/mcp-tests.ts` — covers the bucketing-by-group case.
- `test/instructionsets-tests.ts` — covers the hppa target-string
branch.
## Follow-up
#8690 — drop the `lib/instructionsets.ts` path-based heuristic entirely
in favour of required `instructionSet` properties (the path branch is
Amazon-install-specific and silently mistags compilers on any non-Amazon
layout).
## Test plan
- [x] `npm run test -- --run mcp` — passes (existing + 2 new)
- [x] `npm run test -- --run instructionsets` — passes (existing + 1
new)
- [x] `npm run test:props` — 90/90 pass
- [x] `make pre-commit` — exits 0
- [ ] Drive the staging endpoint with `list_libraries({language:"c++",
match:"boost 1.88"})` and confirm Boost is returned with its versions.
- [ ] Drive the staging endpoint with `list_compilers({match:"gcc 16",
instructionSet:"amd64", latestPerMajor:true})` and confirm `g161`
(x86-64) appears and `hppag1610` does not.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: mattgodbolt-molty <mattgodbolt-molty@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Expose Compiler Explorer's compile, list, shortlink and asm-docs APIs
via a Model Context Protocol (MCP) endpoint mounted at `/mcp`. This lets
MCP-aware clients (Claude, etc.) drive CE directly as a tool.
## Tools exposed at `/mcp`
- **`compile`** — compile source and return assembly / stdout / stderr,
with optional execution.
- `compiler` is **optional**; falls back to the language's
`defaultCompiler` from `list_languages` ("compile this hello world in
C++" is one call).
- `libraries[].version` accepts **either** the version id (`"188"`) or
the human form (`"1.88.0"`) — both work.
- When `execute: true` and the build fails,
`buildResult.stdout`/`stderr` carry the real compiler diagnostics with
**ANSI codes stripped** so an LLM caller sees clean text.
- Caps: `maxAsmLines` / `maxStdoutLines` / `maxStderrLines` with
truncation flags + total counts.
- **`list_compilers`** — with `language`, `instructionSet` (closed enum
from `InstructionSetsList`), `match` (case-insensitive AND-of-tokens;
numeric/dotted-version tokens treated as version-prefix), `lean: true`,
`maxResults`, `latestPerMajor: true`, and `includeExperimental: true`.
- Hard cap of 200 entries on lean responses with a refinement hint —
prevents the unfiltered call from overflowing.
- Each entry exposes `releaseTrack` (`stable | nightly | prerelease |
experimental`) and `supportsExecute` / `supportsBinary`.
- **`list_libraries`** — with `match`, `lean`, `maxResults` (same
lean-cap behaviour).
- **`list_languages`** — minimal listing including `defaultCompiler` and
`compilerCount` per language.
- **`generate_short_url`** — returns `{url}`. Library versions are
normalised before saving.
- **`get_shortlink_info`** — returns saved sessions in the **same shape
`compile` accepts** (`{compiler, options, libraries:[{id, version}]}`)
for direct round-tripping. Multi-pane shortlinks (executors,
conformance, CMake trees) are flattened to the basic compile inputs.
- **`lookup_asm_instruction`** — `instruction_set` is a closed enum
derived from the registered providers (no hand-listed enum values; one
source of truth in `lib/asm-docs/`).
## Implementation
- New `lib/mcp/` module wiring `@modelcontextprotocol/sdk` into the
existing Express router via `StreamableHTTPServerTransport` (stateless
mode — one server per request).
- `lib/mcp/utils.ts`: tokenised `match` with version-prefix matching for
numeric/dotted-version tokens (so `"gcc 14.1"` matches `"gcc 14.1"` and
`"gcc 14.1.0"` but NOT `"gcc 14.10"` or `"gcc 14.0.1"`); `applyCap` with
both per-call lean degradation and an absolute hard cap; `truncateLines`
strips ANSI escapes via the existing `filterEscapeSequences` helper from
`lib/utils.ts`.
- `lib/mcp/library-utils.ts`: `normaliseLibraryVersion` and
`normaliseRequestLibraries` — single source of truth for "accept id or
human version" semantics, used by both `compile` and
`generate_short_url`.
- Schema descriptions are tight (LLM context cost matters) and derive
closed-set enums programmatically from `InstructionSetsList`,
`RELEASE_TRACKS`, and a new `availableAsmDocsKeys` export — no
hand-listed values that can rot.
- Refactor `StorageBase` static helpers (`encodeBuffer`, `isCleanText`,
`getSafeHash`) to module-level functions with type-checked input so MCP
tools can build shortlink hashes without instantiating a storage
backend.
- Expose `ApiHandler.compileHandler` and split out
`getAvailableLanguages()` so MCP can reuse the same code paths the REST
API uses; new `ApiHandler.getDefaultCompilerFor()` for the
compile-default-compiler resolution.
- Browser-friendly CORS on `/mcp`: OPTIONS preflight advertises
`Access-Control-Allow-Methods: POST, OPTIONS` (the shared `cors`
middleware doesn't set Methods); 405 responses on other verbs use the
same Allow header.
- `docs/API.md`: clarify that `/api/shortener` requires a JSON object
body (the prior docs implied but didn't state it).
## Tester feedback addressed
A Claude tester drove the staging deployment through several rounds;
full thread in PR comments. Round-by-round refinements:
- Compile diagnostics surfaced on execute-mode build failures (the
original "silent `Build failed` with empty stderr" bug).
- `execute: true` schema description rewritten to reflect the actual
behaviour.
- Library `version` accepts both forms; clean errors when neither
matches with a sample of available versions.
- `latestPerMajor` rebuilt on top of the `releaseTrack` field added in
#8685, with `includeExperimental` opt-in for c++ proposal forks.
- Lean mode (`lean: true`) for catalog browsing, plus a hard 200-item
cap so even unfiltered calls don't overflow the host.
- Tokenised `match` with version-prefix semantics for
numeric/dotted-version tokens. **Behaviour change** vs the old
`/api/compilers?fields=...` text matching: bare numeric tokens are now
treated as version segments — `"2024"` no longer substring-matches
inside `"v2024beta"`, and `"14.1"` no longer wrongly matches `"14.0.1"`.
Strict improvements but worth a release-note line for callers depending
on the prior loose behaviour.
- ANSI escape code stripping from compile output.
- `instructionSet` as a structured filter (instead of relying on `match`
strings).
- `supportsExecute` / `supportsBinary` on `list_compilers` so an agent
knows whether `execute: true` will work without trying.
- `compilerCount` per language so an agent can tell well-stocked vs
niche languages at a glance.
- Compiler-not-found / library-not-found errors point at the right
`list_*` tool.
## Depends on
#8685 (releaseTrack metadata on `CompilerInfo`) — merged.
## Test plan
- [x] `npm run test -- --run mcp release-track` — all pass (78 + 22)
- [x] `npm run test-min` — full minus expensive, all green
- [x] `make pre-commit` — exits 0
- [x] Multi-round driving on staging via the live MCP endpoint,
including: default-compiler hello-world (no compiler arg), human-form
library version (`"1.88.0"`), broken-compile-with-execute (verifies
buildResult), compile+run with stdin, compile+library Boost 1.90,
parallel `-O0` vs `-O3` diff, `list_compilers latestPerMajor` for
c++/rust/go/csharp, `list_libraries match boost/fmt/json`,
`lookup_asm_instruction MOV amd64`, full `generate_short_url` →
`get_shortlink_info` → re-`compile` round-trip with library
normalisation.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Matt Godbolt <mattgodbolt@hudson-trading.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: mattgodbolt-molty <mattgodbolt-molty@users.noreply.github.com>
## Summary
Adds a structured `releaseTrack: 'stable' | 'nightly' | 'prerelease' |
'experimental'` field on `CompilerInfo` so consumers can distinguish
release tracks that aren't currently separable from the existing
`isSemVer` / `isNightly` / `semver` fields.
Today every compiler with a non-numeric semver and `isNightly=true`
looks the same from the outside: Rust nightly, Rust beta, gcc snapshot,
and gcc's various experimental forks (`gcontracts-trunk`,
`gcc-modules-trunk`, `glambda-p2034-trunk`, ...) all share the same
shape. Anything that wants to ask "what's the canonical newest build of
language X" — a UI badge, the upcoming MCP `latestPerMajor` knob
(#8644), etc. — has no way to tell those apart.
## Implementation
- **Heuristic** in `lib/release-track.ts` driven by `asSafeVer` + the
existing `isSemVer`/`isNightly` flags. Real semvers with a prerelease
segment (e.g. micropython `1.28.0-preview`) are flagged `prerelease`;
semvers containing `trunk`/`main` and the literal `nightly` tag go to
`nightly`; bare `beta`/`alpha`/`rc` tags go to `prerelease`; `isNightly`
with anything else (the c++ experimental forks) goes to `experimental`;
everything else stays `stable`.
- **Override:** `compiler.releaseTrack=` / `group.releaseTrack=` in
`.properties` for cases the heuristic can't reach from structural
fields. Used here for `rustccggcc-master` / `mrustc-master`, where
"master" lives in the compiler id but not the semver field.
- **Backfill** in `loadPrediscovered()` so cached discovery JSON written
before this field existed doesn't break, and so a hand-edited invalid
value gets re-inferred rather than violating the type contract.
- **Tests:** 18 unit tests covering each rule + edge cases (whitespace,
mixed case, prerelease segments) and the `isReleaseTrack` type guard.
## API exposure
`releaseTrack` is on `CompilerInfo` so it appears via
`/api/compilers?fields=all`. It is **not** in the default field set, so
existing API responses are unchanged. UI consumers can opt in.
## Why this is a separate PR
This started as a piece of #8644 (MCP endpoint) — the MCP
`list_compilers` `latestPerMajor` knob needs this distinction to give
clean answers ("newest GCC" should not return a sea of experimental
forks). Rather than hack around the missing metadata in just the MCP
layer, the metadata belongs at the source where other features (UI
badging, etc.) can consume it. #8644 will rebase on top of this.
## Test plan
- [ ] `npm run test -- --run release-track` — 18/18 pass
- [ ] `npm run test:props` — 90/90 pass
- [ ] `make pre-commit` — exits 0 (pre-existing warnings only)
- [ ] Manual sanity check on staging:
`/api/compilers/c%2B%2B?fields=all` returns `releaseTrack` for each
compiler
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: mattgodbolt-molty <mattgodbolt-molty@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary
bronto-refactor emits the refactored source on stdout. The default
`parseOutput` pipeline includes a `FileWithLineMessage` matcher whose
regex (`SOURCE_WITH_FILENAME` in `lib/utils.ts`) is permissive enough to
mistake any C++ member call with a numeric first argument — e.g.
`r.fetch_add(2, std::memory_order_relaxed);` — for a `filename:line:`
reference, treating `r.fetch_add` as a filename and `2` as a line
number. The result was fake red error squigglies in the editor, with the
trailing `, std::memory_order_relaxed);` shown as the diagnostic
message.
`BrontoRefactorTool` now overrides `parseOutput` to drop
`FileWithLineMessage`. `SourceWithLineMessage` is kept so genuine
`<source>:N:M:` diagnostics on stderr still surface.
Reported via the web UI when running bronto-refactor on this:
```cpp
#include <atomic>
void foo(std::atomic<int>& r) {
r.fetch_add(2, std::memory_order_relaxed);
r.fetch_add(4, std::memory_order_relaxed);
}
```
## Out of scope
The `SOURCE_WITH_FILENAME` regex in `lib/utils.ts:154` is genuinely too
loose — any tool whose stdout is source-like (formatters, refactorers)
can hit this. Tightening it (require an extension, or `:`-only
separator) is a separate change since it touches every compiler/tool
that relies on `parseOutput`.
## Test plan
- [x] Added `test/tool-tests.ts` case asserting refactored source
produces no tags.
- [x] Added a second case asserting a real `<source>:N:M:` diagnostic on
stderr is still tagged (so we didn't over-blunt the parser).
- [x] Verified the first test fails on `main` and passes with the fix.
- [x] `npm run ts-check`, `npm run lint`, `npm run test -- --run
tool-tests` all pass.
- [ ] Confirm in the web UI that the original repro no longer shows
squigglies.
cc @fowles
Co-authored-by: mattgodbolt-molty <mattgodbolt-molty@users.noreply.github.com>
We only support running .NET code on CoreCLR and Mono, so we should reject execution on other compilers otherwise this can confuse users a lot.
In addition, removing the --notrimwarn and --noaotwarn so that users can see warnings during NativeAOT compilations, and adding missing arguments to ilc.
- [x] Ensure `CLJ_CACHE` environment variable is set to a writable
directory
- [x] Remove unnecessary `CLJ_CONFIG` environment variable
- [x] ~~Configure location of `clojure_wrapper.clj` (not necessary)~~
- [x] Remove logging of namespace injection - text output causes UI to
show compiler error flag
## Summary
- Fixes#8622: adds assembly documentation entries for the RISC-V `sgt`
and `sgtu` pseudoinstructions so the hover/"View assembly documentation"
feature works instead of showing an error.
- `sgt rd, rs, rt` expands to `slt rd, rt, rs`; `sgtu rd, rs, rt`
expands to `sltu rd, rt, rs`. Entries follow the same shape as the
existing `SGTZ` pseudo entry.
## Test plan
- [x] `npx vitest run test/handlers/asm-docs-tests.ts`
- [x] `npx tsc --noEmit`
- [x] `npm run lint` (no new errors)
---
This PR is authored by an LLM (Claude) acting on behalf of @mattgodbolt.
---------
Co-authored-by: mattgodbolt-molty <mattgodbolt-molty@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
We currently retain only StackMap, but InlineInfo is also useful.
<!-- THIS COMMENT IS INVISIBLE IN THE FINAL PR, BUT FEEL FREE TO REMOVE
IT
Thanks for taking the time to improve CE. We really appreciate it.
Before opening the PR, please make sure that the tests & linter pass
their checks,
by running `make check`.
In the best case scenario, you are also adding tests to back up your
changes,
but don't sweat it if you don't. We can discuss them at a later date.
Feel free to append your name to the CONTRIBUTORS.md file
Thanks again, we really appreciate this!
-->
## Summary
- Fixes the property parser regex to allow `+` in key names (e.g.
`abc+def=etc`)
- The regex from #8387 used `[^=+]+` for key matching, which excluded
`+` from key names entirely
- Changed to a lazy `.+?` match so `+` is only treated as the `+=`
operator when immediately before `=`
This is a small targeted fix and does not address all edge cases with
the `+=` syntax.
## Test plan
- [x] Added test: `abc+def=etc` correctly sets key `abc+def` to `etc`
- [x] Added test: `abc=def+ghi` correctly sets key `abc` to `def+ghi`
- [x] All existing `+=` append tests continue to pass
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
A `catchswitch` instruction has the form:
catchswitch within none [label %catch.start] unwind to caller
The LLVM IR parser then extracts the labels out of this expression.
However, the regex is too greedy, and (incorrectly) matches
`%catch.start]`. We update the regex to match what LLVM's lexer does.
The [language reference](https://llvm.org/docs/LangRef.html#identifiers)
states that quoted identifiers can include `\xx` escapes, however as far
as I can tell [that is *not*
true](413cafa462/llvm/lib/AsmParser/LLLexer.cpp (L391-L421)),
so I have elected not to support them.
For the following program (targetting WASM, compiled with
`-fwasm-exceptions`):
```c
void bar(int x);
void foo() {
try {
bar(1);
} catch(int x) {
}
}
```
**Before:**
<img width="1492" height="411" alt="A CFG of the above program. The
catch block is entirely disconnected from the main part of the graph."
src="https://github.com/user-attachments/assets/ad9038cc-f372-4109-bc25-328537bcea88"
/>
**After:**
<img width="1286" height="639" alt="A CFG of the above program. The
catch block is now correctly connected to the rest of the graph."
src="https://github.com/user-attachments/assets/27cce7b3-dd67-4c1c-928c-93f7eaa53fa2"
/>