Fix ColonyChat.register() (two-step migration) + version drift - #4
Merged
Conversation
`ColonyChat.register()` has been broken and the test suite has been red. It called `ColonyClient.register`, which colony-sdk has removed. The pin was `colony-sdk>=1.18.0,<2`, so every install resolved to a version without it. The three register tests patched that exact attribute, so `patch()` raised `AttributeError` and they failed — unlike the sibling repos where a bare MagicMock invented the missing method and hid it. Nobody saw the failure only because CI last ran on 2026-06-09, against an older SDK, and no commit has landed since. A dormant repo's last green tick says nothing about today. WHAT CHANGED Added `register_begin()` / `register_confirm()` as the pair callers should prefer. `register_begin` reserves the handle and returns api_key, claim_token and a convenience key_fingerprint, leaving the account PENDING — it holds the handle but cannot send or read. `register_confirm` activates it by proving the key was kept. Splitting them is the whole point for a library. Unlike the CLIs in sentinel / colony-agent-template / crewai-colony, colony-chat does not own the caller's storage, so it cannot do begin → persist → confirm itself. Exposing both halves is what lets the caller put durable storage in between, which is the guarantee the flow exists to provide. This matches the shape pydantic-ai-colony already uses for the same reason. `register()` is kept and works again, implemented as the two halves back to back. Its docstring is explicit that the one-shot activates before anything is written down and therefore gives up that guarantee. Keeping it avoids turning a bug fix into an API removal, and it was already non-functional, so nothing that currently works changes shape. Also fixed: `base_url` now reaches the confirm call, not just begin. Confirm is a separate HTTP request, so a self-hosted Colony would have activated against production. TESTS Nine new tests. `register_begin` raises ColonyChatError when the response carries no api_key or claim_token — returning the partial dict would hand back something that looks like a successful registration and is permanently pending. `REGISTER_ALREADY_ACTIVE` is tolerated as the server's documented idempotent guard, paired with a control that a *different* API error still propagates. Both controls were mutation-tested rather than eyeballed: replacing the code check with a bare `except ColonyAPIError: return active` turns exactly that one control red and nothing else. ALSO - `colony-sdk>=1.32.0,<2`. - chat.thecolony.cc → chat.thecolony.ai in the description, Homepage, Documentation URL and docs. Both hosts were probed first: root and /skill.md return 200 on each, and skill.md is byte-identical in size. Deliberately NOT the author email — thecolony.ai publishes no MX record. 123 tests pass; ruff check, ruff format --check and mypy all clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRn9SBFGaxRwZbwRsKNJ7b
….2.0 Separate defect, found while fixing registration. Unrelated to that change — kept as its own commit so it can be dropped independently. `pyproject.toml` declared 0.2.0 and `colony_chat/_version.py` declared 0.1.3, with nothing comparing them. The drift is already published: the wheel on PyPI for colony-chat 0.2.0 carries `Version: 0.2.0` in its metadata and reports `__version__ == "0.1.3"` when imported. Verified by downloading the published artifact, not by reading the repo. Anything that logs or branches on the runtime version — a plugin checking a feature floor, a bug report quoting itself — has been told the wrong number since 0.2.0 shipped. Neither direction of this drift is self-announcing. `_version.py` ahead makes a tag unpublishable under its own number; pyproject ahead publishes cleanly and then lies. It was the second, which is the quiet one. FIX - `_version.py` → 0.2.0, matching pyproject. - `tests/test_version_consistency.py` compares the two on every push, before anything is tagged. By tag time the fix means retagging something already published, which is why this is a unit test and not a release-workflow step. - `test_version_exported` no longer hardcodes the literal. Two places to update per release is what produced this, and that assertion was the one that got missed — it happily pinned 0.1.3 while the package shipped as 0.2.0. The guard carries its own control: a check that reads one source twice, or a regex that matches nothing and compares None to None, passes forever and certifies nothing. Mutation-tested by restoring _version.py to 0.1.3 — the guard and both control cases fail, and pass again once corrected. tomllib is 3.11+ and CI runs 3.10, so pyproject is read with a regex anchored to the first version key after [project]. 123 tests pass; ruff check, ruff format --check and mypy all clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRn9SBFGaxRwZbwRsKNJ7b
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two independent defects, one per commit so either can be dropped.
1.
ColonyChat.register()is broken, and the suite is already redIt calls
ColonyClient.register, which colony-sdk has removed. The pin wascolony-sdk>=1.18.0,<2, so every install resolves to a version without it.Unlike the sibling repos — where a bare
MagicMockinvented the missing method and hid the break — the three register tests here patched that exact attribute, sopatch()raisesAttributeErrorand they fail. The suite fails on a clean checkout ofmaintoday (3 failed, 107 passed against colony-sdk 1.32.0).Nobody saw it because CI last ran 2026-06-09, against an older SDK, and no commit has landed since. A dormant repo's last green tick says nothing about the present.
Found while fixing the same SDK removal in
sentinel#20; the sibling fix iscolony-agent-template#26.The migration
Added
register_begin()/register_confirm()as the pair callers should prefer.register_beginreserves the handle and returnsapi_key,claim_tokenand a conveniencekey_fingerprint, leaving the account pending — it holds the handle but cannot send or read.register_confirmactivates it by proving the key was kept.Splitting them is the point, and it's specifically why a library differs from a CLI here. sentinel, colony-agent-template and crewai-colony can do
begin → persist → read back → confirminternally because they own a config file. colony-chat doesn't own the caller's storage, so collapsing the two halves would confirm before anything was written — defeating the gate entirely. Exposing both is what lets storage sit in between. Same shapepydantic-ai-colonyalready uses, for the same reason.register()is kept and works again, implemented as both halves back to back, with a docstring that is explicit about giving up that guarantee. Keeping it avoids turning a bug fix into an API removal — and since it was already non-functional, nothing that currently works changes shape. Happy to remove it instead if you'd rather not ship a one-shot at all — that's a genuine design call and I'd take your steer.Also fixed:
base_urlnow reachesconfirm, not justbegin. Confirm is a separate HTTP request, so a self-hosted Colony would have activated against production.Tests
Nine new.
register_beginraisesColonyChatErrorwhen the response has noapi_keyorclaim_token— returning the partial dict would hand back something that looks like a successful registration and is permanently pending.REGISTER_ALREADY_ACTIVEis tolerated as the documented idempotent guard, paired with a control that a different error still propagates.Mutation-tested rather than eyeballed: swapping the code check for a bare
except ColonyAPIError: return activeturns exactly that one control red and nothing else.2. Version drift — the package has been lying about its own version
Separate commit, unrelated to the above.
pyproject.tomlsaid 0.2.0,colony_chat/_version.pysaid 0.1.3, nothing compared them. This is already published — verified by downloading the artifact rather than reading the repo:Neither direction of this drift announces itself:
_version.pyahead makes a tag unpublishable under its own number; pyproject ahead publishes cleanly and then lies. It was the quiet one.Fixed
_version.pyto 0.2.0 and addedtests/test_version_consistency.py, which compares the two on every push — a unit test rather than a release-workflow step, because by tag time the fix means retagging something already published.test_version_exportedno longer hardcodes the literal; two places to update per release is what caused this, and that assertion was the one that got missed.The guard ships with its own control — a check that reads one source twice, or a regex that matches nothing and compares
NonetoNone, passes forever and certifies nothing. Mutation-tested by restoring_version.pyto 0.1.3: the guard and both controls fail, then pass again once corrected.tomllibis 3.11+ and CI runs 3.10, so pyproject is read with a regex anchored to the first version key after[project].Also
chat.thecolony.cc→chat.thecolony.aiin the description,Homepage,Documentationand docs. Both hosts probed first — root and/skill.mdreturn 200 on each,skill.mdidentical in size. Not the author email —thecolony.aipublishes no MX record, so mail to the.aiform bounces.Verification
123 tests pass (110 → 123, and 3 that were failing now pass).
ruff check,ruff format --checkandmypy colony_chatall clean — I ran the exact CI gates locally.No version bump or release here — the CHANGELOG entry sits under
Unreleased. Releasing is yours to call.Leaving this for @jackparnell — I don't self-merge in
TheColonyCC/*.🤖 Generated with Claude Code
https://claude.ai/code/session_01TRn9SBFGaxRwZbwRsKNJ7b