The Compiler Stopped Talking About Go: GALA in August and September (0.72 → 0.79)
Twelve releases moved GALA's errors out of generated Go and into your .gala source, then into JSON, the IDE, and a Claude Code plugin.
Until 0.75, this GALA program compiled, linked, and printed v=1:
val x = 1
Println(s"v=${x +}")
The trailing + just vanished. Invalid source produced a working binary.
The cause was plumbing, not anything deep. The string-interpolation re-parser
removed the default error listeners and never installed its own. On top of
that, the expression rule wasn't anchored to end-of-input, so x + parsed as
x and stopped there. Nobody noticed because nobody writes ${x +} on purpose.
It still counts as the worst kind of compiler bug: the program was wrong and
nothing said so.
It stood out, but it wasn't unusual. Any language that compiles to another
language picks up a whole class of these bugs, where the error is real but
shows up in the wrong place, in the wrong vocabulary, or not at all.
Go is a very good backend and a mediocre narrator. If the transpiler lets a
mistake through, go build catches it and reports it against a generated file
you never wrote, with type names you never used.
GALA has shipped twelve releases since the last of these posts, 0.73.0 through 0.79.0. Most of the work had one goal: report the problem in your file, in GALA's terms, to whatever is reading it. These days that might be you, your editor, or an AI agent editing the code for you.
Errors in your file, not in gen/
Here's the before picture. A GALA package under internal/ was always
private to its parent tree, because Go enforces that rule. But Go enforced it on
the generated code, so the error looked like this:
gen\main.gen.go:6:8: use of internal package gala-build-workspace/gen/sub/internal/deep not allowed
That names a file you didn't write and a path that only exists inside the build
workspace. In 0.73 the check moved into GALA and became GALA-E0041, reported at
your import line:
error[GALA-E0041]: package "example.com/myapp/sub/internal/deep" is internal to "example.com/myapp/sub" and cannot be imported from "example.com/myapp"
--> main.gala:3:8
|
3 | import "example.com/myapp/sub/internal/deep"
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ internal packages are private to their parent tree
The rule is Go's, exactly as cmd/go applies it. Only whole path elements count
(internalize stays public), the last internal element wins, and the
standard library gets no exemption.
0.75 applied the same idea to three mistakes that people coming from other languages make all the time. All three used to fail badly.
Unparenthesized lambda parameters. x => x * 2 used to produce up to four
cascading parser errors, and none of them mentioned parentheses. Now you get
one:
error[GALA-E0042]: lambda parameters must be parenthesized
--> main.gala:5:20
|
5 | Println(xs.Map(x => x * 2))
| ^ use `(x) => ...`
|
= hint: use `(x) => ...`; GALA always parenthesizes a lambda's parameter list, including a single parameter
Why not just accept the bare form? Because case x => body would become
ambiguous, and the parser would silently pick the wrong reading. I'd rather have
a clear error than a grammar that sometimes guesses wrong.
A type name called like a constructor. Array(1, 2, 3) used to build a
corrupt struct literal from three of Array's four private fields, and the
failure came back in Go's words. Now:
error[GALA-E0043]: Array is a type, not a constructor
--> main.gala:6:14
|
6 | val xs = Array(1, 2, 3)
| ^^^^^ use `ArrayOf(...)`
|
= hint: use `ArrayOf(...)`; GALA constructs values through named functions, so a type name is never callable
A method the type doesn't have. This one used to come from go build,
reported against the generated expression, including auto-unwrap .Get() calls
the user never wrote. Now GALA reports it and suggests the closest real method:
error[GALA-E0044]: Array has no method Sise
--> main.gala:7:16
|
7 | Println(xs.Sise())
| ^^^^ did you mean `Size`?
|
= hint: did you mean `Size`?
When nothing is close, it lists what the type does declare.
Two fixes in the same release go with these. First, a diagnostic raised inside
an interpolation now points at the right line. A bare len inside s"..." on line
6 used to put its caret on package main, because the embedded expression was
re-parsed in a fresh stream where everything sat on line 1. Second, gala build
and gala run now print a file, line, and source frame for syntax errors. Before,
only gala transpile did.
The same diagnostics, for a machine
A framed error with a caret is great for a person reading a terminal. For a program it's text that has to be scraped. More and more of the programs reading compiler output are coding agents, and scraping is where they make mistakes.
So 0.75 added a structured form of each diagnostic. With --json on build,
run and transpile, the Sise error above becomes:
{
"diagnostics": [
{
"severity": "error",
"code": "GALA-E0044",
"message": "Array has no method Sise",
"hint": "did you mean `Size`?",
"file": "main.gala",
"line": 7,
"column": 16,
"endColumn": 20,
"docsUrl": "https://gala.fyi/docs/errors/gala-e0044/"
}
]
}
Two lookup commands came with it:
gala explain <code>prints the full reference page for any diagnostic, offline. It takesGALA-E0044,E0044or just44, and--listenumerates every code.gala doc <package>[.<Type>]prints what a package exports. That turns "what can I call on this?" into a lookup instead of a guess.
The website also serves an llms.txt now. It covers the places where GALA looks
like Go or Scala but behaves differently, which is where generated code most
often goes wrong.
Documentation reaches the editor
The standard library has over 1,500 doc comments. Until 0.76, the lexer threw every one of them away, so no tool could show them.
Now they flow through. GALA uses Go's convention exactly: a // run directly
above a declaration documents it. So the comments you already write show up in
hover, completion and signature help:
func (Option) GetOrElse(defaultValue T) T
GetOrElse returns the option's value if the option is Some, otherwise returns
the result of evaluating defaultValue.
defaultValue: the default value to return if the option is empty.
*Package: std*
That last name: description line is a parameter doc. Signature help shows it
next to that parameter. It's only treated as a parameter doc when name matches
a real parameter, so Note: and Example: stay part of the prose.
Adding documentation exposed how much hover was missing. Before 0.76 it returned nothing for methods, struct fields, local bindings, package aliases and import paths. In practice, every method call in every GALA file hovered to nothing. Hover now uses the same type resolution as completion, so the two can't disagree about what an expression is.
The point releases after that were mostly about builder chains, the style every GALA server, client and config API is written in:
gsrv.NewServer().
WithName("gala-kv").
WithShutdownTimeout(timeout).
ServeTCPOn(addr, (c) => Serve(c, st)) match { ... }
In 0.76.0, hovering over any link in that chain showed nothing. The chain starts with a package qualifier rather than a value, and the resolver looked that qualifier up as if it were a receiver type. It found no methods and gave up on every link after it. Fixing that turned up more problems:
- Go to definition looked for the dot on the cursor's own line. In a chain, that line has no dot; it's at the end of the line above. It only seemed to work because it fell back to a global name search, which returned whichever type happened to declare a method with the same name.
- A comment above a statement was being read as part of it. Ordinary sentences
end in a period, and the line joiner treats a trailing
.as a member access. val shutdownTimeout = 15 * time.Secondhovered asint. Arithmetic took its type from the left operand. Go's rule is that an untyped constant takes the type of the typed operand. The generated Go was always correct, because Go re-typed it. GALA's own inference was wrong, and hover and inlay hints are built on it.
0.77 made completion write your code, not just list names. Completing a call inserts its required parameters as named arguments, with a tab stop for each:
srv.NewServer().
WithName(name = ▮)
ServeTCPOn(addr = ▮, handler = ▮)
Parameters with defaults are left out, because the call can omit them. Inside an argument list, completion offers the parameters you haven't passed yet first.
This work also turned up a transpiler bug. Methods with their own type parameters ignored named arguments and bound everything by position:
arr.FoldLeft(f = (acc, x) => acc + x, initial = 0)
// was: Array_FoldLeft(arr, func(acc any, x any) any {...}, 0) — swapped, untyped
That output swapped the arguments, and it used any, which GALA's codegen is
never allowed to emit unless the source asks for it. Generic methods now bind by
name, fill in defaults, and report unknown, duplicate and missing arguments like
every other call. The line above prints 10.
A plugin for Claude Code
The last two releases, 0.78 and 0.79, both came out today, and they connect all of the above to an agent.
GALA now ships a Claude Code plugin that runs
gala lsp. While Claude edits .gala files, it gets two things:
- Diagnostics after every edit. Parse errors, non-exhaustive matches,
GALA-E*checks like the data-race check from the last post: all of them reach Claude on its next tool call, so it fixes them without waiting for a build. - Lookups instead of guesses. Claude's LSP tool can ask the server for hover (inferred types and docs), go to definition, find references, and document or workspace symbols.
/plugin marketplace add martianoff/gala
/plugin install gala@gala
gala new projects are already set up for it. They include a
.claude/settings.json that declares the marketplace, so Claude Code offers the
plugin once you trust the folder.
Building it surfaced some LSP server gaps that no editor had exposed.
Programs split across files had no cross-file navigation. workspace/symbol
wasn't implemented. The outline found names by substring, which put main on
the package main line. And the shutdown response had neither result nor
error, which JSON-RPC 2.0 forbids. All four are fixed in 0.78.
0.79 fixed a less obvious problem: having tools doesn't mean Claude will use
them. With only the plugin, Claude reliably received diagnostics but rarely
asked the server anything. On a real task it guessed at APIs and grepped the
standard library cache. The plugin now includes a short skill (about 74 tokens
per session) that tells Claude to use its Edit tool rather than cat >> (only
editor edits trigger diagnostics), to look up types through the server, and to
finish with gala build.
I ran the same task in fresh sessions with and without the skill. Without it, Claude made zero LSP calls. With it, Claude went from a type's definition to its method list to a hover, edited through Edit, built, and did it for about half the cost. That's one task, so treat it as an anecdote, not a benchmark. Still, the difference was large enough to ship.
The limit is the same as in the terminal. Diagnostics only cover what GALA's
transpiler checks. An error that only the Go compiler finds still needs
gala build, and the skill says so.
The rest
Conventional Go project layouts build from the root. For a project with
packages under internal/ and the program under cmd/<name>/, gala build
at the root used to fail with no Go files in <workspace-hash>/gen. Now it finds
the main package itself. If there are several, it lists them and asks you to
pick one. gala test handles the same layout too.
Standing guards against type problems. Since 0.74, several guards run
continuously instead of relying on individual fixes. One checks that no
generated variable, field, map or slice element widens to any unless the source
asked for it. Another checks that every error code has a docs page, and every
docs page has a code. There's also a guard that transformer state gets restored
on error paths. And GALA_WARN_TYPES=1 lists every expression the transpiler
couldn't type. That list works as a regression detector rather than a backlog.
Every entry today is in a program that compiles and runs, because Go's type
checker finishes the job, so an increase is the thing to act on.
An inference bug that depended on a different file. GALA's strings
package shares a name with Go's. Once any file in a package imported GALA's,
a sibling file's Go strings.SplitN was typed as returning Array[string],
while the code generator still emitted Go's []string. Adding a file could
break a different file you hadn't touched. Fixed in 0.73.1: a qualified call now
resolves through that file's own imports.
The IntelliJ plugin works on 2025.3+ again. On newer platforms, every
PSI-based feature crashed as soon as you opened a .gala file. The cause was a
final class in the platform that the ANTLR adaptor subclasses. That adaptor has
exactly one release on Maven Central, so upgrading it wasn't an option, and the
plugin no longer uses it. The plugin's unit tests also run in CI again.
SECURITY.md. Security reports now go through private GitHub advisories.
The scope includes soundness holes in the guarantees GALA advertises (the race
checker, immutability, exhaustiveness). Running untrusted GALA is explicitly out
of scope, because GALA is not a sandbox.
Where this leaves things
With a transpiled language, it's easy to treat the target language's compiler as the real checker, and the language's own checks as nice extras. That works right up until a user has to debug your generated code to understand their own mistake.
These twelve releases pushed the other way. Mistakes are now reported by GALA, against your source, with a code you can look up and a fix you can apply. That same diagnostic now reaches the terminal, the JSON output, the IDE, and an agent halfway through an edit, without any of them scraping text.
Some rough edges remain. Anything Go catches that GALA doesn't still shows up as a Go error after a full build. The type-resolution inventory isn't empty. And the agent integration is new enough that its best evidence so far is one task. Still, the direction is clear: by the time Go sees the code, it should have nothing left to report.
The error reference is at gala.fyi/docs/errors, the Claude Code plugin setup is in ide/claude-code, and the playground runs GALA without installing anything.