Files
solo-c/.gitignore
T
Luxferre ecc48cd472 fix six defects and add an offline test suite
Four settings read from the environment (endpoint, model, reasoning,
scratch dir) pointed straight at getenv() memory instead of a copy,
and both setstr() and the cleanup at exit called free() on them.
Changing one of them from inside the session therefore freed the
environment block: "/endpoint http://x.example" with SOLO_ENDPOINT
set died inside the handler, and SOLO_TMPDIR killed the process on the
way out without a slash command at all. Reproduced on musl, both are
copies now, and the two post-load env re-reads go through setstr() so
they stop leaking the value create() already stored.

The generic parameter handler typed every number with
json_object_new_double, so "/max_tokens 4096" reached the endpoint as
4096.0 and "/seed 3" as 3.0. A number written without a decimal point
or an exponent now stays an integer, which is what the OpenAI schema
asks for; 0.7, .5 and 1e3 are still floats, and a word is still a
string.

The deadline for a command was time(0) + SOLO_TIMEOUT, and time()
counts whole seconds, so the effective limit was the timeout minus
whatever fraction of the second the command happened to start in.
Measured with SOLO_TIMEOUT=3, commands died after 2.38s, 2.47s, 2.68s
and 3.19s depending on the phase. rp() takes its deadline from
clock_gettime(CLOCK_MONOTONIC) now, which is also immune to the clock
being stepped underneath it.

A SOLO_TIMEOUT below 1 gave rp() a deadline already in the past, so
every command failed at once with "still running after 0s", and
atoi on anything unparseable lands there too. Below 1 now means no
limit. The API read, which passed time(0) + 1e9 to mean "never",
passes no deadline at all.

Only the child called setpgid(0,0), so a kill(-k, ...) issued before
the child reached it found no process group and was dropped, leaving
the command running as an orphan. A standalone harness confirms both
halves: without the parent claim the signal is lost and the child
runs to completion, with it the child dies. The parent claims the
group too, which is the belt to the child's braces.

hc() wrote the response envelope with one uncounted write(), so a
signal arriving mid-write truncated the JSON and the parent reported
"request failed" for a request that had in fact succeeded.

Both link steps write to <name>.new and rename it. Overwriting a
running executable fails with ETXTBSY, which is exactly what happens
when the agent rebuilds the binary it is itself running from, and a
failed link used to take the working binary with it. make clean now
removes the temp names as well.

test.sh holds 13 offline tests: no API key and no network, every line
handed to the agent starting with / or !, each test in its own
directory because the session file lives in the cwd. They cover the
four crash paths, integer/float/string typing, a staggered-start
sweep for the early timeout, SOLO_TIMEOUT=0 and a negative value,
process group cleanup after a timeout, and the tool description
quoting the real timeout. make test runs them. The README documents
the timeout semantics, the integer typing and the new build step.

Verified: 13 passed, 0 failed against the new binary, and 8 of the
13 fail against the previous one, which is what makes them regression
tests rather than decoration. Still silent under -Wall -Wextra and
under -Wshadow -Wformat=2 -Wcast-align -Wwrite-strings
-Wstrict-prototypes -Wmissing-prototypes -Wredundant-decls -Wundef.
Built and tested on Alpine gcc 15.2 (musl) with libcurl 8.22.0,
json-c 0.19 and readline 8.3.
2026-10-04 06:06:06 +00:00

79 lines
1.3 KiB
Plaintext

# Build artifacts.
# Both names are produced by the Makefile targets `solo` and `static`.
# Anchor on the root so a directory called solo is still tracked.
/solo
/solo-static
# Half-written link targets, left behind only by a failed link.
/solo.new
/solo-static.new
*.o
*.a
*.so
*.so.*
# Prebuilt libcurl.a for `make static`, regenerated by `make static-deps`
# (see mkstaticdeps.sh). Large, and tied to the machine that built it.
/static-deps/
/curl-*/
# Per-directory session state: the live model parameters, the tool schema and
# the full conversation transcript, which can contain secrets. Runtime data, so
# it must never be committed.
.solo_sess.json
*.solo_sess.json
# Readline history, in case HOME or XDG is pointed here in some setups.
.solo_history
.history
# Scratch space the agent is told to use.
/solo-tmp/
/tmp/
# Editor and tooling droppings.
*~
*.bak
*.orig
*.rej
*.swp
*.swo
.*.sw?
.\#*
\#*\#
# Vim.
.vim/
*.vim
*.swo
# Emacs.
*.elc
*.symc
.\#*
\#*\#
# Compiled binaries and debug output from ad-hoc experiments.
/core
/core.*
vgcore.*
a.out
gdb.core
# Coverage and profiling, if instrumentation is ever added.
*.gcda
*.gcno
*.gcov
*.profraw
*.profdata
# OS cruft.
.DS_Store
Thumbs.db
desktop.ini
# Secrets and local overrides. Commit a .env.example instead.
.env
.env.local
SOLO_API_KEY
SOLO_SYSPROMPT.local