Skip to content

Fix ColonyChat.register() (two-step migration) + version drift - #4

Merged
jackparnell merged 2 commits into
mainfrom
fix/two-step-registration
Aug 11, 2026
Merged

Fix ColonyChat.register() (two-step migration) + version drift#4
jackparnell merged 2 commits into
mainfrom
fix/two-step-registration

Conversation

@ColonistOne

Copy link
Copy Markdown
Contributor

Two independent defects, one per commit so either can be dropped.

1. ColonyChat.register() is broken, and the suite is already red

It calls ColonyClient.register, which colony-sdk has removed. The pin was colony-sdk>=1.18.0,<2, so every install resolves to a version without it.

Unlike the sibling repos — where a bare MagicMock invented the missing method and hid the break — the three register tests here patched that exact attribute, so patch() raises AttributeError and they fail. The suite fails on a clean checkout of main today (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 is colony-agent-template#26.

The migration

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 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 → confirm internally 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 shape pydantic-ai-colony already uses, for the same reason.

begun = ColonyChat.register_begin(handle="...", display_name="...")
secrets_store.put("COLONY_CHAT_API_KEY", begun["api_key"])
saved = secrets_store.get("COLONY_CHAT_API_KEY")   # read it BACK
ColonyChat.register_confirm(claim_token=begun["claim_token"], key_fingerprint=saved[-6:])
client = ColonyChat(api_key=saved)

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_url now reaches confirm, not just begin. Confirm is a separate HTTP request, so a self-hosted Colony would have activated against production.

Tests

Nine new. register_begin raises ColonyChatError when the response has 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 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 active turns 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.toml said 0.2.0, colony_chat/_version.py said 0.1.3, nothing compared them. This is already published — verified by downloading the artifact rather than reading the repo:

wheel metadata  Version: 0.2.0
runtime         __version__ = "0.1.3"

Neither direction of this drift announces itself: _version.py ahead makes a tag unpublishable under its own number; pyproject ahead publishes cleanly and then lies. It was the quiet one.

Fixed _version.py to 0.2.0 and added tests/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_exported no 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 None to None, passes forever and certifies nothing. Mutation-tested by restoring _version.py to 0.1.3: the guard and both controls fail, then 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].

Also

chat.thecolony.ccchat.thecolony.ai in the description, Homepage, Documentation and docs. Both hosts probed first — root and /skill.md return 200 on each, skill.md identical in size. Not the author emailthecolony.ai publishes no MX record, so mail to the .ai form bounces.

Verification

123 tests pass (110 → 123, and 3 that were failing now pass). ruff check, ruff format --check and mypy colony_chat all 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

ColonistOne and others added 2 commits August 11, 2026 14:29
`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
@jackparnell
jackparnell merged commit 2f8cbe9 into main Aug 11, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants