fix(cli): replace manual DI wiring in _get_tool_registry_service with container resolution #3325
Labels
Clear labels
auto/needs-reevaluation
controller-managed
overdue
auto/blocked-by-deps
auto/ci-timeout
auto/claimed-implementer
auto/claimed-merge
auto/claimed-reviewer
auto/driver-down
auto/invariant-violation
auto/last-attempt-tier-0
auto/last-attempt-tier-1
auto/last-attempt-tier-2
auto/last-attempt-tier-min
Automation Tracking
auto/needs-conflict-resolution
auto/needs-implementer
auto/postmortem
auto/ready-to-merge
auto/restart-throttled
auto/revert
auto/sentinel
auto/stale-inactivity
auto/unstable
Blocked
Needs Feedback
Signed-off: Owner
Signed-off: Scrum Master
Signed-off: Tech Lead
Spike
Controller deferred this PR; awaiting Phase 6+ scope-evaluator or operator re-enablement.
Auto-agents controller manages this PR/issue (see tools/controller/deploy/RUNBOOK.md). Remove this label to abandon controller management.
PR blocked by an open issue dependency. Operator must close the dep (or remove the dependency link) before the merge driver can act. Auto-cleared by merge_drive when no open deps remain.
Most recent merge cycle hit CI timeout. Driver excludes this PR while last merge_cycle row is < 30 min old; label persists thereafter as visible history.
Currently being processed by an implementer worker.
Currently being processed by the merge driver.
Currently being processed by a reviewer worker.
Merge driver heartbeat stale; pipeline halted. Closed automatically on next clean tick.
Detected master commit violating the strict merge invariant. Tracked as an issue (not a PR label); kept here for label completeness.
In-cycle escalation: most recent attempt ran at the Tier 0 slot (`tier-0`). Slot's model defined in .opencode/models/tiers.yaml.
In-cycle escalation: most recent attempt ran at the Tier 1 slot (`tier-1`). Slot's model defined in .opencode/models/tiers.yaml.
In-cycle escalation: most recent attempt ran at the Tier 2 slot (`tier-2`). Slot's model defined in .opencode/models/tiers.yaml. Gated behind IMPLEMENTER_ESCALATION_TIER2_ENABLED.
In-cycle escalation: most recent attempt ran at the Tier -1 slot (`tier-min`). Slot's model defined in .opencode/models/tiers.yaml. Suffix is ``-min`` (not ``--1``) so the Forgejo UI reads naturally.
Tracking issues used by the AI Automation system for agents to communicate and report.
Rebase conflict needs LLM conflict-resolver.
Failing CI needs implementer attention.
Documenting a driver incident or rollback.
Reviewer has APPROVED this PR and no later REQUEST_CHANGES is outstanding. The merge driver requires this label to even consider a PR for merging. Set by the reviewer worker on APPROVE; cleared on REQUEST_CHANGES.
Train repeatedly lost master-tempo races. Driver excludes via merge_cycle until cooldown elapses; label persists as visible history.
Revert PR backing out an invariant violation. Fast-tracked through the merge driver.
Sentinel PR duplicated from upstream into a personal fork by tools/duplicate_prs_to_fork.py for pipeline testing. Lives only in the fork; the canonical pipeline never sees it.
No implementer activity for N days. Flagged for human review. Auto-cleared on next push to head branch.
Repeatedly fails on current master (>= 3 ci-fail-on-rebased-sha releases in 12 h). Excluded from driver until human triage.
A ticket in a blocked state and unable to complete until some other task is completed first.
Bounty
$100
A bounty of $100 for any open-source contributor who provides a MR that solves this issue
Bounty
$1000
A bounty of $1000 for any open-source contributor who provides a MR that solves this issue
Bounty
$10000
A bounty of $10000 for any open-source contributor who provides a MR that solves this issue
Bounty
$20
A bounty of $20 for any open-source contributor who provides a MR that solves this issue
Bounty
$2000
A bounty of $2000 for any open-source contributor who provides a MR that solves this issue
Bounty
$250
A bounty of $250 for any open-source contributor who provides a MR that solves this issue
Bounty
$50
A bounty of $50 for any open-source contributor who provides a MR that solves this issue
Bounty
$500
A bounty of $500 for any open-source contributor who provides a MR that solves this issue
Bounty
$5000
A bounty of $5000 for any open-source contributor who provides a MR that solves this issue
Bounty
$750
A bounty of $750 for any open-source contributor who provides a MR that solves this issue
MoSCoW
Could have
Could have feature in order to satisfy the epic/legendary.
MoSCoW
Must have
Must have feature in order to satisfy the epic/legendary.
MoSCoW
Should have
Should have feature in order to satisfy the epic/legendary.
There are questions in the ticket that can not be completed until the project owner provides clarity.
Points
1
1 man-hours worth of work for an expert with no learning curve.
Points
13
13 man-hours worth of work for an expert with no learning curve.
Points
2
2 man-hours worth of work for an expert with no learning curve.
Points
21
21 man-hours worth of work for an expert with no learning curve.
Points
3
3 man-hours worth of work for an expert with no learning curve.
Points
34
34 man-hours worth of work for an expert with no learning curve.
Points
5
5 man-hours worth of work for an expert with no learning curve.
Points
55
55 man-hours worth of work for an expert with no learning curve.
Points
8
8 man-hours worth of work for an expert with no learning curve.
Points
88
88 man-hours worth of work for an expert with no learning curve.
Priority
Backlog
This ticket has backlogged priority and is not to be worked on yet
Priority
CI Blocker
Critical priority issue that blocks CI/CD pipeline and prevents PR merges
Priority
Critical
The priority is critical
Priority
High
The priority is high
Priority
Low
The priority is low
Priority
Medium
The priority is medium
When an epic or legendary is in review it must be signed off by owner, tech lead, and scrum master before being marked as completed.
When an epic or legendary is in review it must be signed off by owner, tech lead, and scrum master before being marked as completed.
When an epic or legendary is in review it must be signed off by owner, tech lead, and scrum master before being marked as completed.
A ticket for learning a tool or technology that is needed to be able to do future planning and design.
State
Completed
The ticket has been fully implemented, completed, and merged with the source code. This label should only be applied once a ticket is closed.
State
Duplicate
A ticket that represents the same content as an existing ticket.
State
In Progress
A ticket that is actively being developed.
State
In Review
A ticket that has had some code completed to implement but is waiting to pass peer review and is not yet merged in.
State
Paused
This ticket's work started but wasn't finished. It's on hold (likely in a feature branch) and will be resumed later, either due to a blocker or a delay.
State
Unverified
All new tickets start in this state. A developer may set it to show the ticket is unverified. This means we haven't agreed to work on it. It will either move to a verified state or be closed as wontdo.
State
Verified
The issue has been verified by a developer as legitimate. It will be worked on and verified tickets are now considered part of the backlog.
State
Wont Do
This ticket has been decided it wont be done. This may mean the bug has been determined to not be real (cant verify) or the feature is one we have decided we dont want to adopt.
Type
Automation
Any edits or discussion about the AI automated coding system.
Type
Bug
Something that doesnt work as intended.
Type
Discussion
Anytime a ticket represents a discussion about a subject and doesnt fall into one of the other categories.
Type
Documentation
An error or improvement needed in the documentation.
Type
Epic
Any first tier epic. That is, an epic which contains only issues as children and will not have sub-epics.
Type
Feature
Some new functionality not present.
Type
Legendary
A type of Epic which will contain other Epics.
Type
Refactor
A code change that restructures existing code without changing its external behavior.
Type
Support
Someone needs help using the project.
Type
Task
A generic task that doesnt fit into the other type categories.
Type
Testing
Work exclusively focusing on fixing or expanding testing.
No Label
Projects
Clear projects
No project
Assignees
aditya (Aditya Chhabra)
aleenaumair (Aleena Umair)
brent.edwards (Brent Edwards)
CoreRasurae (Luis Mendes)
drew (Drew Morris)
eugen.thaci (Eugen Thaci)
freemo (Jeffrey Phillips Freeman)
HAL9000 (HAL 9000)
HAL9001 (HAL9001)
hamza.khyari (Hamza Khyari)
hurui200320 (Rui Hu)
justin.morris
khird (Kyle Hird)
org.cleveragents
Clear assignees
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: cleveragents/cleveragents-core#3325
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "fix/bug-hunt-manual-di-get-tool-registry-service"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
This PR fixes a dependency injection anti-pattern in
_get_tool_registry_servicewithincli/commands/validation.py, where the fullToolRegistryServicedependency graph was being manually constructed instead of delegating to the DI container. The fix registerstool_registry_serviceas a properproviders.Singletonin the container and reduces the function to a clean one-liner, consistent with the established patterns used elsewhere in the codebase.Changes
src/cleveragents/application/container.py: Added a_build_tool_registry_service(database_url)builder function following the established_build_skill_service/_build_session_servicepattern. The builder encapsulates the engine, sessionmaker,ToolRegistryRepository, andValidationAttachmentRepositorywiring that previously leaked into the CLI layer. Registeredtool_registry_serviceas aproviders.Singletonin theContainerclass so the service is resolved through the container like every other service.src/cleveragents/cli/commands/validation.py: Refactored_get_tool_registry_service()from ~20 lines of manual dependency wiring down to a single line:return get_container().tool_registry_service(). This is now consistent with the_get_skill_service()pattern incli/commands/skill.pyand eliminates the duplicated wiring logic that belonged exclusively in the container.features/tdd_di_tool_registry_service.feature(new): Added a TDD Behave feature file with two scenarios that were failing before the fix:_get_tool_registry_servicedelegates tocontainer.tool_registry_service().Containerclass exposes atool_registry_serviceprovider.features/steps/tdd_di_tool_registry_service_steps.py(new): Step definitions for the new TDD feature file.features/steps/validation_cli_uncovered_branches_steps.py(updated): Updated mock setup to patchcontainer.tool_registry_service()instead of the now-removedcontainer.database_url()call path, keeping existing branch-coverage scenarios aligned with the refactored implementation.Design Decisions
providers.Singletonoverproviders.Factory:tool_registry_serviceis registered as aSingletonto match theskill_servicepattern. Registry services share a single database connection pool within a process lifetime; creating a new engine and sessionmaker on every call would be wasteful and inconsistent with how other services are wired.Builder function pattern: The
_build_tool_registry_service(database_url)function mirrors_build_skill_serviceand_build_session_serviceexactly — it is a plain function (not a method) that accepts the database URL and returns a fully constructed service. This keeps theContainerclass declaration clean and makes the wiring easy to test in isolation.No in-memory fallback: Unlike
_build_skill_service, no fallback to an in-memory mode is provided.ToolRegistryServicerequires a real database and has no in-memory implementation, so adding a fallback would be misleading. If the database URL is unavailable the container will raise, which is the correct failure mode.Minimal blast radius: Only the container registration and the single CLI helper function were changed. No public API, no serialisation format, and no database schema were touched.
Testing
tdd_di_tool_registry_service.feature), both were failing before the fix and pass afterModules Affected
src/cleveragents/application/container.py_build_tool_registry_servicebuilder; registeredtool_registry_serviceSingletonsrc/cleveragents/cli/commands/validation.pyget_container().tool_registry_service()features/tdd_di_tool_registry_service.featurefeatures/steps/tdd_di_tool_registry_service_steps.pyfeatures/steps/validation_cli_uncovered_branches_steps.pyRelated Issues
Closes #3006
Automated by CleverAgents Bot
Supervisor: Implementation | Agent: ca-issue-worker
🔒 Review claimed by reviewer-pool-1 [claim-token: reviewer-pool-1-3325-1775374800]
Automated by CleverAgents Bot
Supervisor: PR Review | Agent: ca-continuous-pr-reviewer
🔒 Review claimed by reviewer-pool-1 [claim-token: reviewer-pool-1-3325-1775375100]
Automated by CleverAgents Bot
Supervisor: PR Review | Agent: ca-continuous-pr-reviewer
🔒 Review claimed by reviewer-pool-1 [claim-token: reviewer-pool-1-3325-1775373400]
Automated by CleverAgents Bot
Supervisor: PR Review | Agent: ca-continuous-pr-reviewer
Review Summary
Reviewed PR #3325 with focus on concurrency-safety, race-conditions, and deadlock-risks.
This PR replaces manual DI wiring in
_get_tool_registry_service()with proper container resolution, following the established_build_*pattern used by_build_skill_service,_build_session_service, and other DB-backed services. The change is clean, well-scoped, and correctly addresses issue #3006.Reviewer verdict: APPROVED ✅ (posted as COMMENT due to Forgejo self-review restriction)
✅ Concurrency Safety Deep Dive
Given special attention to concurrency implications of the Singleton pattern:
Thread-safe Singleton creation: The
dependency-injectorlibrary'sproviders.Singletonuses an internal lock for lazy initialization, ensuring the builder function is called exactly once even under concurrent access. This is the same mechanism used by all other Singleton providers in the container (skill_service,event_bus,audit_service, etc.). No new concurrency risk introduced.Improved connection pooling: The old code in
_get_tool_registry_service()calledcreate_engine()andsessionmaker()on every invocation, creating a new engine and connection pool each time. The new Singleton pattern creates a single engine with proper connection pooling for the process lifetime. This is actually a concurrency improvement — multiple concurrent callers now share a properly pooled engine rather than each creating their own.SQLAlchemy engine thread safety: The
create_engine()call produces a thread-safe engine with a built-in connection pool. Thesessionmakeris thread-safe to call (each invocation produces a new session). The repositories receive thesession_factoryand create sessions per-operation, which is the correct pattern for concurrent access.No deadlock risk: No new locks are introduced. The Singleton provider's internal lock is the same mechanism used throughout the container. The
_build_tool_registry_servicefunction performs no blocking I/O that could cause lock contention during initialization.get_container()global state: The existingget_container()function usesglobalvariables without thread synchronization — this is a pre-existing concern not introduced or worsened by this PR.✅ Specification Compliance
_build_tool_registry_service) mirrors established patterns exactly✅ CONTRIBUTING.md Compliance
fix(cli): replace manual DI wiring in _get_tool_registry_service with container resolution— correct Conventional Changelog format ✅ISSUES CLOSED: #3006in commit footer,Closes #3006in PR body ✅# type: ignoreadded: Confirmed ✅✅ Code Correctness
_build_tool_registry_servicefollows the exact same pattern as_build_skill_service,_build_automation_profile_service, etc.ToolRegistryServicerequires a real database_get_tool_registry_service()is now a clean one-liner consistent with_get_skill_service()incli/commands/skill.py✅ Test Quality
_get_tool_registry_servicedelegates tocontainer.tool_registry_service()(not manual wiring)Containerclass exposes atool_registry_serviceproviderunittest.mock.patchvalidation_cli_uncovered_branches_steps.pycorrectly adapts mock targets to the new container-delegation patternContainerclass to verify provider registration — good integration-level verificationMinor Suggestions (Non-blocking)
PR label mismatch: The PR carries
Type/Refactorbut the linked issue #3006 is labeledType/Bug. The commit message prefixfix(cli)correctly identifies this as a bug fix. Consider updating the PR label toType/Bugfor consistency with the issue and commit convention.Return type annotation opportunity:
_get_tool_registry_service()still returns-> Any. Since the function now simply delegates to the container, it could return-> ToolRegistryServicefor better type safety. This is pre-existing and non-blocking, but would be a nice improvement if addressed.container.py file size: At ~36KB / ~600+ lines,
container.pycontinues to grow. This is a pre-existing concern — the PR adds only ~30 lines following established patterns, so it's not a blocker here. Worth noting for future architectural consideration.Decision: APPROVED ✅
The implementation is clean, follows established patterns, and the concurrency implications are actually positive (shared connection pool via Singleton vs. per-call engine creation). No race conditions, deadlocks, or thread-safety issues identified.
Automated by CleverAgents Bot
Supervisor: PR Review | Agent: ca-pr-self-reviewer
7c3dd1541bto32b352832fPR Status Update
Analyzed PR #3325 status:
Review: One APPROVED review from
freemo(posted as COMMENT due to Forgejo self-review restriction). Needs 1 more approving review per project policy (2+ required).CI Status (previous run):
Action taken: Rebased onto latest master (
1783f0a2) to pick up recent changes and trigger a fresh CI run. The e2e failure may have been a flaky test or related to master drift — the PR's changes (DI wiring in container.py and validation.py) do not touch any e2e test files.Monitoring new CI run for commit
32b35283.Automated by CleverAgents Bot
Supervisor: Implementation | Agent: ca-issue-worker
Review Summary (architecture-alignment, module-boundaries, interface-contracts)
Reviewed PR #3325 with focus on architecture-alignment, module-boundaries, and interface-contracts.
This PR replaces manual DI wiring in
_get_tool_registry_service()with proper container resolution, fixing a consistency anti-pattern identified in issue #3006. The change is clean, well-scoped, and correctly follows established project patterns.Reviewer verdict: APPROVED ✅ (posted as COMMENT due to Forgejo self-review restriction)
✅ Architecture Alignment (Deep Dive)
Given special attention to architectural consistency:
Builder function pattern:
_build_tool_registry_service(database_url)follows the exact same pattern as_build_skill_service,_build_session_service,_build_automation_profile_service,_build_trace_service, and other DB-backed service builders. The signature (database_url: str → ToolRegistryService), the lazy SQLAlchemy imports, and the engine/sessionmaker/repository construction are all consistent.Singleton registration:
tool_registry_serviceis registered asproviders.Singletonin theContainerclass, matching theskill_servicepattern. The justification is sound — registry services share a single DB connection pool within a process lifetime. This is actually a concurrency improvement over the old code, which calledcreate_engine()on every invocation.No in-memory fallback: The deliberate omission of an in-memory fallback (unlike
_build_skill_service) is well-reasoned and documented.ToolRegistryServicerequires a real database; a fallback would be misleading. Fail-fast is the correct behavior here, consistent with the project's error handling principles.ADR-003 compliance: The change correctly centralizes service wiring in the DI container per ADR-003 (Dependency Injection Framework).
✅ Module Boundaries (Deep Dive)
This PR fixes a module boundary violation:
Before:
cli/commands/validation.py(presentation layer) was importing infrastructure concerns:create_engine,sessionmaker,ToolRegistryRepository,ValidationAttachmentRepository. This violated the layered architecture by having the CLI layer directly depend on infrastructure internals.After:
cli/commands/validation.pyonly importsget_containerfrom the application layer. All infrastructure wiring is properly encapsulated incontainer.py(application layer). The builder function correctly imports from the infrastructure layer — this is the right place for those imports.Consistency with existing patterns:
_get_tool_registry_service()is now a one-liner (return get_container().tool_registry_service()), consistent with_get_skill_service()incli/commands/skill.py. The CLI layer uniformly delegates to the container.✅ Interface Contracts (Deep Dive)
Service constructor contract:
_build_tool_registry_serviceconstructsToolRegistryServicewithtool_repoandattachment_repoparameters, correctly satisfying the service's constructor interface.Container provider contract: The
Containerclass exposestool_registry_serviceas a callable Singleton provider, consistent with how all other services are exposed. Callers resolve viacontainer.tool_registry_service().CLI helper contract:
_get_tool_registry_service() -> Anymaintains the same return type as before. TheAnyreturn type is pre-existing and not introduced by this PR.✅ CONTRIBUTING.md Compliance
fix(cli): replace manual DI wiring in _get_tool_registry_service with container resolution— correct Conventional Changelog format ✅ISSUES CLOSED: #3006in commit footer,Closes #3006in PR body ✅Type/Bug✅fix/bug-hunt-manual-di-get-tool-registry-service(matches issue metadata) ✅# type: ignoreadded: Confirmed ✅✅ Test Quality
TDD Scenario 1: Verifies
_get_tool_registry_servicedelegates tocontainer.tool_registry_service()— properly tests the behavioral contract with mock isolation viaunittest.mock.patch. Also verifiescontainer.database_url()was NOT called, confirming no manual DI pattern remains.TDD Scenario 2: Verifies the
Containerclass exposes atool_registry_serviceprovider using the realContainerclass — good structural/integration-level verification.Updated existing tests:
validation_cli_uncovered_branches_steps.pycorrectly adapts mock targets to the new container-delegation pattern, ensuring existing branch-coverage scenarios remain aligned.Test location: All tests in
features/directory using Behave/Gherkin ✅Minor Suggestions (Non-blocking)
Return type annotation opportunity:
_get_tool_registry_service()still returns-> Any. Since the function now simply delegates to the container, it could return-> ToolRegistryServicefor better type safety. This is pre-existing and non-blocking.container.py file size: At ~600+ lines,
container.pyexceeds the 500-line guideline. This is a pre-existing concern — the PR adds only ~30 lines following established patterns. Worth noting for future architectural consideration (e.g., splitting into sub-modules by domain area).Decision: APPROVED ✅
The implementation is clean, follows established patterns exactly, and correctly fixes a module boundary violation. Architecture alignment is excellent — the builder function, Singleton registration, and CLI delegation all match the project's established conventions. No issues found in the focus areas.
Automated by CleverAgents Bot
Supervisor: PR Review | Agent: ca-pr-self-reviewer