Installing Goose: The Docs Were Wrong Three Times

I set out to install Goose, the open-source AI agent, by following the official docs. Three times the instructions didn’t
match what my terminal actually did. Here’s what went wrong, how I worked around each one, and why “read the error, not just the docs” saved the day.

What Goose is

Goose is a small programming language built on top of Go by a single developer (calico32/goose, 3 stars on GitHub). It reads like a mix of Python and Ruby: blocks close with end, and strings use $name for interpolation.

I went in expecting a five-minute install. Instead, the documentation was wrong three separate times, and I learned more from that than from the language itself.

My setup: Debian, Go 1.27.0, a conda (base) environment active (harmless for Go).

The install: when the README is out of date

The README said to run go run cli/goose.go. After cloning, that file did not exist:

$ go run cli/goose.go --help
stat cli/goose.go: no such file or directory

The author had restructured the project since that README was published (May 2024). My next guess, go run ., failed too:

$ go run . --help
package github.com/calico32/goose is not a main package

In Go, only a file declaring package main is a runnable program. The top-level main.go declares package goose, so it is a library, not the program.

The fix was to stop trusting the README and read the Makefile, the recipe the author actually uses to build:

$ grep -F goose.go Makefile
	go build $(LDFLAGS) -o build/goose ./bin/cmd/goose.go

A side lesson: plain grep goose.go also matched lines like goose goose-docs. Grep treats . as a regex wildcard meaning any character. Adding -F searches for the literal text.

The working install:

git clone https://github.com/calico32/goose.git
cd goose
make goose
mkdir -p ~/.local/bin
cp build/goose ~/.local/bin/
which goose    # /home/lvydvy/.local/bin/goose

Using ~/.local/bin keeps it in my user account, with no sudo needed.

Testing it: predict, run, compare

I wrote small test files with heredocs and predicted each result before running it. Quoting the delimiter (<< 'EOF') mattered: without the quotes, bash would have replaced $name with its own empty variable before Goose ever saw it.

cat > ~/test1.goose << 'EOF'
fn add(a, b = 2)
  return a + b
end
println(add(1))
let name = "Dennis"
println("hello $name")
println(10 ** 30)
EOF
FeatureResultEvidence
memo cachingWorksFibonacci example: only 101 calls for 100 numbers
Big integersWorks100th Fibonacci and 10 ** 30 exact, past the 64-bit limit
Default parameters, repeat N times, $name interpolationWorksAll outputs matched predictions
to / step rangesWorks, though listed as “future”0 to 10 step 2 printed 0 2 4 6 8
int.parseWorks, though listed as “future”Printed 123
int.maxNot builtPrinted null
Missing propertiesSilently return nullA deliberate typo, int.mxa, printed null with no error

One prediction I got wrong: the docs said integers were 64-bit, so I expected the 93rd Fibonacci number to overflow and go negative. It didn’t. I cross-checked the 100th number with a Python one-liner, and both gave 354224848179261915075.

One small bug: goose -version reports the current time as the “built at” time, not the actual build time.

Security lessons hiding in the docs

The project’s syntax.goose file is half a feature tour and half a wishlist. Reading it closely turned up four things worth flagging:

  1. A mislabeled hash. It claims hash.sha256("foo") returns 0beec7b5…a33. That is 40 hex characters, 160 bits, and it is actually the SHA-1 hash of “foo”. A real SHA-256 hash is 64 hex characters. Recognizing a hash type by its length is a basic forensics and password-cracking skill.
  2. Importing code from a URL. A planned import "https://…" would fetch and run remote code on every run. If that host were compromised, every program using it would run the attacker’s code. That is a textbook supply-chain risk.
  3. Unescaped macros. The docs note that foo!(2 - 3, 5 + 4) gives -7, not -9, because the arguments are pasted in as raw text. That is the same root cause as SQL injection, which the sql! macro example then tries to guard against.
  4. Silent nulls. In the working language, a typo like int.mxa returns null instead of raising an error. In a check like “is this user an admin?”, a silent null can become a logic flaw. Python’s AttributeError is the safer default.

Verdict

Goose is a neat way to see how a language is built. Its folders map directly onto the pipeline: scanner breaks code into tokens, parser builds a tree, interpreter runs it. That same pattern shows up when parsing packets in Black Hat Python.

As a practical tool, it is not there yet: most of the advertised features are still planned. My study time stays with Python for Black Hat Python and the AWS DVA-C02 exam.

The bigger takeaway is the method. The README was wrong about the file path, the comments were wrong about 64-bit integers, and the “future” label was wrong about what already worked. The Makefile and small, predicted tests were what I could trust. That habit of verifying behavior instead of believing documentation is the core of security work.