Skip to content

fix(agents): self-heal duplicate active handles so the new unique index cannot brick boot (#2, #10)#1841

Merged
jaylfc merged 1 commit into
devfrom
fix/registry-index-selfheal
Jul 16, 2026
Merged

fix(agents): self-heal duplicate active handles so the new unique index cannot brick boot (#2, #10)#1841
jaylfc merged 1 commit into
devfrom
fix/registry-index-selfheal

Conversation

@jaylfc

@jaylfc jaylfc commented Jul 16, 2026

Copy link
Copy Markdown
Owner

Folds two findings from the multi-day audit of the identity epic.

#2 (boot-brick, the important one): ux_agent_active_handle is created in _post_init (runs in lifespan). Handle uniqueness among active agents is a NEW invariant, so a DB created before it can hold two active rows with the same handle; CREATE UNIQUE INDEX then raises IntegrityError and the controller fails to boot on every restart until manual DB surgery. This directly gates deploying the epic to any existing install.

Fix: _migration_v4_dedupe_active_handles blanks the handle on all but the oldest active row per handle (rows stay active; blanked handles are excluded from the partial index and reclaimable), logging each change; then the index creation is itself guarded (catch IntegrityError, warn, continue) so nothing unanticipated can brick boot. Write-time uniqueness still applies.

#10: register / PATCH returned an unhandled 500 on a duplicate handle; now 409.

Regression tests added: a DB seeded with two active hermes rows boots, keeps the oldest, blanks the duplicate, and enforces uniqueness afterward. Full registry + mint + auth-requests suite: 227 passed.

…annot brick boot

The new ux_agent_active_handle partial unique index (handle uniqueness among
active agents is a new invariant added by the consent flow) is created in
_post_init, which runs in lifespan. A pre-invariant DB can legally hold two
active rows with the same handle; CREATE UNIQUE INDEX then raises
IntegrityError and the controller fails to boot on every restart until manual
DB surgery — unacceptable for a no-terminal appliance.

- Add _migration_v4_dedupe_active_handles: blank the handle on all but the
  oldest active row per handle (rows stay active; blanked handles are excluded
  from the index and can be reclaimed), logging each change.
- Guard the index creation itself (catch IntegrityError, warn, continue) so an
  unanticipated duplicate degrades gracefully rather than bricking boot;
  write-time uniqueness still applies.
- register/PATCH now return 409 (not an unhandled 500) when a handle is already
  owned by another active agent.

Regression tests: a DB seeded with two active 'hermes' rows now boots, keeps
the oldest, blanks the duplicate, and enforces uniqueness afterward.

Audit findings #2 (boot-brick) and #10 (500 -> 409).
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@jaylfc, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 8 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7c1281ab-2060-40f4-837a-9b4cf15719eb

📥 Commits

Reviewing files that changed from the base of the PR and between 5d5dec3 and 5132924.

📒 Files selected for processing (3)
  • tests/test_registry_governance_lifecycle.py
  • tinyagentos/agent_registry_store.py
  • tinyagentos/routes/agent_registry.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/registry-index-selfheal

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gitar-bot

gitar-bot Bot commented Jul 16, 2026

Copy link
Copy Markdown

Important

You are using the Gitar free plan. Upgrade to unlock code review, CI analysis, auto-apply, custom automations, and more.

Gitar

role=body.role,
capabilities=body.capabilities or [],
)
except aiosqlite.IntegrityError:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Broad IntegrityError catch assumes the violation is always the active-handle index.

store.register only performs an INSERT, so today the only UNIQUE constraint it can hit at this point is ux_agent_active_handle (canonical_id collisions are pre-empted by the retry loop above). The 409 + "handle is already owned by another active agent" message is therefore accurate now, but this handler will also swallow any future integrity violation (e.g., a new column with a UNIQUE constraint) and still report it as a handle conflict, which could mislead operators debugging a real schema error.

Consider checking e.args / the error string (or catching only the specific index) so the response reflects the actual constraint, or add a comment documenting that handle-collision is the sole expected cause. Not blocking.

@kilo-code-bot

kilo-code-bot Bot commented Jul 16, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 1 Issue Found | Recommendation: Address before merge (non-blocking)

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
tinyagentos/routes/agent_registry.py 249 Broad IntegrityError catch assumes the violation is always the active-handle index; a future unique constraint would still be reported as a handle conflict.
Files Reviewed (3 files)
  • tinyagentos/agent_registry_store.py - dedupe migration + guarded index creation
  • tinyagentos/routes/agent_registry.py - 409 mapping on duplicate handle
  • tests/test_registry_governance_lifecycle.py - regression tests

The boot-brick fix (#2) is correct: the dedupe migration runs before the index creation and non-destructively blanks duplicate active handles (keeping the lowest-id owner), and the index creation is itself guarded so an unforeseen duplicate degrades to a warning instead of bricking boot. The concurrent-approve race is already handled in agent_auth_requests.py via the same set_status('active') guard, and both register/PATCH now return 409 on a duplicate active handle. No correctness or security defects found.

Fix these issues in Kilo Cloud


Reviewed by hy3:free · Input: 77.2K · Output: 4.2K · Cached: 223.1K

@jaylfc
jaylfc merged commit 76f744d into dev Jul 16, 2026
11 checks passed
@jaylfc
jaylfc deleted the fix/registry-index-selfheal branch July 16, 2026 09:24
jaylfc added a commit that referenced this pull request Jul 16, 2026
…MA (boot-brick) (#1852)

The canvas and task stores put their element_id index in SCHEMA:
  CREATE INDEX ... ON project_canvas_elements(project_id, element_id)
  CREATE INDEX ... ON project_tasks(project_id, element_id)
BaseStore runs SCHEMA (executescript) BEFORE _post_init, so on an existing
pre-element_id database the index creation raised
'no such column: element_id' before the _post_init ALTER could add the column,
crashing controller boot after an upgrade. Reproduced live: the Pi bricked on
startup right after pulling this code (fresh installs were fine because SCHEMA
creates the table WITH element_id, so CI never exercised the migration path).

Move both indexes out of SCHEMA into _post_init, created after the ALTER.
Same class of bug and fix as the registry active-handle index (#1841).
Regression test seeds a pre-element_id DB and asserts both stores boot,
migrate the column, and build the index.
jaylfc added a commit that referenced this pull request Jul 16, 2026
…MA (boot-brick) (#1853)

The canvas and task stores put their element_id index in SCHEMA:
  CREATE INDEX ... ON project_canvas_elements(project_id, element_id)
  CREATE INDEX ... ON project_tasks(project_id, element_id)
BaseStore runs SCHEMA (executescript) BEFORE _post_init, so on an existing
pre-element_id database the index creation raised
'no such column: element_id' before the _post_init ALTER could add the column,
crashing controller boot after an upgrade. Reproduced live: the Pi bricked on
startup right after pulling this code (fresh installs were fine because SCHEMA
creates the table WITH element_id, so CI never exercised the migration path).

Move both indexes out of SCHEMA into _post_init, created after the ALTER.
Same class of bug and fix as the registry active-handle index (#1841).
Regression test seeds a pre-element_id DB and asserts both stores boot,
migrate the column, and build the index.
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.

1 participant