-
Notifications
You must be signed in to change notification settings - Fork 17
Description
Feature Request: Allow custom hooks to be loaded from ~/.ccproxy directory
Summary
Custom hooks defined in ccproxy.yaml cannot be loaded from Python files in ~/.ccproxy/ without workarounds, even though LiteLLM callbacks in config.yaml can load adjacent scripts automatically.
Current Behavior
When I create a custom hook file at ~/.ccproxy/custom_hooks.py:
# ~/.ccproxy/custom_hooks.py
def forward_oauth_dynamic(data, user_api_key_dict, **kwargs):
# Custom OAuth logic
return dataAnd reference it in ~/.ccproxy/ccproxy.yaml:
ccproxy:
hooks:
- ccproxy.hooks.rule_evaluator
- ccproxy.hooks.model_router
- custom_hooks.forward_oauth_dynamic # <-- FAILSThe proxy fails to start with:
ImportError: No module named 'custom_hooks'
Expected Behavior
Custom hooks should load from ~/.ccproxy/ the same way LiteLLM callbacks do.
For comparison, this works in ~/.ccproxy/config.yaml:
litellm_settings:
callbacks:
- jsonl_logger.jsonl_logger.handler # Loads from ~/.ccproxy/jsonl_logger/LiteLLM successfully imports jsonl_logger from the config directory because it adds the config directory to sys.path before importing callbacks.
Root Cause
The load_hooks() method in src/ccproxy/config.py uses importlib.import_module() directly without adding the config directory to sys.path:
def load_hooks(self) -> list[tuple[Any, dict[str, Any]]]:
# ...
module = importlib.import_module(module_path) # Fails for adjacent scriptsProposed Solution
Add the config directory to sys.path before importing hooks, matching LiteLLM's behavior:
import sys
def load_hooks(self) -> list[tuple[Any, dict[str, Any]]]:
"""Load hook functions from their import paths."""
# Add config directory to sys.path for custom hooks
config_dir = str(Path.home() / ".ccproxy")
if config_dir not in sys.path:
sys.path.insert(0, config_dir)
loaded_hooks = []
for hook_entry in self.hooks:
# ... existing code ...Current Workarounds
-
Copy to package directory (fragile, lost on upgrade):
cp ~/.ccproxy/custom_hooks.py ~/.local/share/uv/tools/claude-ccproxy/lib/python3.11/site-packages/ccproxy/
Then use
ccproxy.custom_hooks.forward_oauth_dynamicin yaml. -
Set PYTHONPATH (requires environment setup):
export PYTHONPATH="$HOME/.ccproxy:$PYTHONPATH"
Both workarounds are inconvenient compared to LiteLLM's seamless adjacent-file loading.
Documentation Reference
The README states:
Custom rules (and hooks) are loaded with the same mechanism that LiteLLM uses to import the custom callbacks - they are imported by the LiteLLM python process as named module from within its virtual environment, or as a python script adjacent to config.yaml.
This suggests adjacent-file loading should work, but currently only the "named module from virtual environment" path works for ccproxy hooks.
Environment
- ccproxy version: 1.2.0
- Python: 3.11
- OS: macOS
- Installation method:
uv tool install claude-ccproxy
Related
- LiteLLM callback loading works correctly for adjacent files
- This affects anyone wanting to write custom hooks without modifying the installed package