Skip to content

keel v0.13.1

Choose a tag to compare

@github-actions github-actions released this 01 Sep 10:05
· 237 commits to main since this release
9c11ead

Built from 9c11ead. Version binds to this hash:
keel --version reports keel 0.13.1+9c11ead4d0b6 [release].

Install

Download all wheels from this release into one directory, then install the
keel_trader wheel by path:

pip install --find-links . ./keel_trader-0.13.1-py3-none-any.whl
keel versions

keel versions — not keel --version — is the check: it reports every
keel distribution in the venv and exits non-zero if a sibling was left behind at
an older version, which --version cannot see. Upgrading an existing
deployment: see "Deploying a new version" in the README.

⚠️ Never install by bare name. The distribution is keel-trader; the name
keel on PyPI belongs to an unrelated project, so pip install keel fetches
someone else's package. A build reporting DIRTY or [checkout] is not this
release and must not be run against live funds.

Configure

config.yaml is attached to this release: the production config, in
auto_trade.mode: confirm — keel previews every order and waits for your
approval. Drop it beside the install (or run keel init-config --live), put
your CDP key in a git-ignored .env, then:

keel migrate     # existing database: apply schema migrations
keel init        # fresh deployment: write config + seed candidate rules

Seeded rules start as candidate and trade nothing until you promote them.

Other changes

fix(cli): a reader that hangs up is not a crash (#663) (#664)

Closes #663. Unblocks the 0.13.0 release.

The Windows leg died mid-render:

File "keel\commands\brokers.py", line 396, in brokers_list
    click.echo(line)
OSError: [Errno 22] Invalid argument
Exception ignored while flushing sys.stdout: OSError: [Errno 22]

Not encoding, and nothing in the 0.13.0 payload. release.yml ran brokers list | head -1; head takes one line and exits, closing the pipe, and on Windows the next write raises OSError with EINVAL — errno 22, not 32 — which no except BrokenPipeError catches.

Why now: brokers list went from 4 lines to 35 when #624 added the readiness block. With four the process finished before head exited; with thirty-five it doesn't. v0.12.2 passed the identical check for that reason alone.

Red herring, recorded so nobody re-chases it: brokers.py does emit a non-ASCII " · " (U+00B7). It's in v0.12.2 too, whose Windows leg passed.

Two halves

The workflow captures before filtering, at both call sites. Every other check in that file already did — these two were the exceptions. The second, brokers list | grep -qi 'fake', carries the same trap and is masked only because fake is absent so grep reads to EOF; it would have failed exactly when it found the thing it looks for.

The app grows keel.cli.main, and both entry points route through it — the console script and the frozen bundle, which is the build that failed. It catches BrokenPipeError and OSError with EPIPE/EINVAL, and deliberately not a blanket OSError: a disk filling mid-write raises ENOSPC and an operator needs to hear about it.

The devnull redirect isn't decoration — without it the interpreter flushes the dead stream at shutdown and raises a second time, after the handler ran. That's the trailing Exception ignored line above.

One of my own pins failed its mutations first

The end-to-end brokers list | head -1 test passed with the handler deleted and with the devnull redirect deleted. On POSIX, 35 short lines fit the 64 KiB pipe buffer, so the write never fails and the handler is never reached. It was decorative.

Rewritten to inject the failure, with the real path moved to a subprocess — run in-process the redirect destroys pytest's own captured stdout (OSError: [Errno 9] from capture.py), which is the fix working as designed.

All four mutations now fail:

mutation
EINVAL dropped the original bug
blanket except OSError would swallow ENOSPC
handler removed traceback returns
devnull dropped the trailing shutdown failure returns

test_freeze gained the reason the frozen entry must call main and not cli — calling cli directly leaves the one build without the handler, and that build is the one that died.

Honest coverage note

The Windows EINVAL path is exercised by constructing the OSError, not by a real closed pipe: this suite runs on Linux and macOS, where errno 22 never occurs. Stated in the module docstring rather than implied.

Verification

5111 passed, 3 skipped · ruff check clean · mypy clean (379 files) · release.yml strict-parses under GitHub's own rules.

chore(release): 0.13.1 — the desktop legs, all three of them (#665)

v0.13.0's release job succeeded — wheels and config.yaml are published and keel update works. Then all three desktop legs failed, so the release is missing two DMGs, the Windows installer and zip, and the three SHA256SUMS-* files. The tag is immutable and correctly refused a re-dispatch, so the fix ships as 0.13.1.

Why all three failed — not what the Windows traceback suggested

The smoke step ran brokers list | head -1. head exits after one line and closes the pipe, and #624 had grown that command's output from 4 lines to 35, so the write now lands after the reader is gone.

leg symptom
windows-latest loud — OSError: [Errno 22] Invalid argument, full traceback
macos-14 / macos-15-intel silent — output ends at ok: 7 keel distributions, then exit code 1

The macOS legs are the same bug wearing a POSIX face. The step runs under bash --noprofile --norc -e -o pipefail, so the broken pipe killed keel, pipefail took the pipeline's failing status, and -e aborted the step with nothing printed — the failure was a signal, not an exception.

Which half of #663 was actually load-bearing

main()'s closed-stdout handler exits 141 — and 141 is still non-zero, so under pipefail it would have aborted the step exactly the same way.

The workflow half is the fix: capture, then filter, so no reader hangs up at all. The handler is still worth having — keel brokers list | head -1 is something an operator types and it shouldn't traceback — but it's the smaller half, and #663 stated that ordering backwards. Only these logs showed it.

main now pipes binary output nowhere in release.yml, at either former call site — including the grep -qi 'fake' one, which carried the same trap and was masked only because fake is absent so grep read to EOF.

The bump

Every workspace pyproject.toml and every exact-version sibling pin moves, per the lesson from the 0.13.0 bump where the root alone left the workspace disagreeing with itself (test_workspace_siblings_are_pinned_to_the_exact_version).

uv_build>=0.10.4,<0.13.0 is the build backend constraint and is deliberately untouched.

Verification

5111 passed, 3 skipped · ruff check clean · mypy clean (379 files).

After merging

Actions → Release → Run workflow, 0.13.1. If the desktop artifacts are wanted on the release rather than as workflow artifacts, set desktop to publish-unsigned — the default build smoke-tests them without attaching.