| name | cocosearch-debugging |
| description | Use when debugging an error, unexpected behavior, or tracing how code flows through a system. Guides root cause analysis using CocoSearch semantic and symbol search. |
Debugging with CocoSearch
Pre-flight Check
- Resolve index name (use the resolved name for all operations):
- Try
cocosearch.yaml for indexName field -- if found, use it
- If no config file, call
list_indexes() and match the current project's directory name against available indexes. The MCP tools auto-derive index names from directory paths (e.g., my-project/ -> my_project), so a match is likely if the repo was indexed without a config file.
- If no match found, the project is genuinely not indexed -- offer to index it. Do NOT abandon CocoSearch tools just because
cocosearch.yaml is missing.
list_indexes() to confirm project is indexed
index_stats(index_name="<resolved-name>") to check freshness
- No index → offer to index before debugging
- Stale (>7 days) → warn: "Index is X days old -- results may not reflect recent changes. Want me to reindex first?"
-
Check dependency freshness — call get_file_dependencies on any known file from the error context:
get_file_dependencies(file="<file-from-error>", depth=1)
- If response contains
warnings with type deps_outdated or deps_branch_drift:
Warn: "Dependency data is outdated — call chain tracing may be incomplete. Want me to re-extract dependencies first?"
- If response contains
warnings with type deps_not_extracted:
Note: "No dependency data found. I'll use search-based call tracing instead. Dependency data can improve tracing accuracy — want me to extract dependencies?"
- If no warnings: Proceed normally (use dependency Fast Path when applicable).
-
Linked index health (if cocosearch.yaml has linkedIndexes):
- Check the
warnings array from index_stats() for entries starting with "Linked index"
- If stale/missing: warn user — "Linked index 'X' is stale/missing. Cross-project results may be incomplete. Want me to reindex?"
Step 1: Understand the Symptom
Parse what the user is reporting. Different inputs require different extraction:
If error message or exception:
- Extract error type:
ValueError, TypeError, NullPointerException, etc.
- Extract key identifiers: function names, class names, variable names from stack trace
- Extract semantic context: what operation was being performed?
If unexpected behavior:
- Extract what's happening vs. what should happen
- Extract any mentioned functions, files, or data flows
- Identify the symptom's observable effects
If user provides stack trace:
- Parse the call stack: which functions are involved?
- Identify entry point (top of stack) and failure point (bottom of stack)
- Extract file paths and line numbers if present
Store extracted information:
- Identifiers: List of function names, class names, variables mentioned
- Semantic query: Natural language description of the problem
- Suspected area: File paths or module names mentioned
Present back to user: "I see the error is <error-type> in <function-name> when <operation>. Let me search for where this originates."
Step 2: Cast Wide Net
This is the critical discovery phase. Run both semantic and symbol searches simultaneously to find the strongest leads.
Semantic search for symptom:
search_code(
query="<user's symptom description>",
use_hybrid_search=True,
smart_context=True,
limit=10
)
Cross-project search: If linkedIndexes is configured in cocosearch.yaml, searches automatically expand to linked indexes. For bugs spanning shared libraries, pass index_names=["app", "shared-lib"] to trace across boundaries.
Query rewrite: If the optional query-rewrite controller is enabled, pass rewrite_query=False when searching precise terms (exact error strings, identifiers, symbol_name/symbol_type filters) so they are matched verbatim. No effect when the controller is disabled.
Symbol search for each identifier:
For each identifier extracted from the symptom (function names, class names, error types):
search_code(
query="<identifier>",
symbol_name="<identifier>*",
use_hybrid_search=True,
smart_context=True,
limit=5
)
Synthesize results:
- Which files appear in BOTH semantic and symbol searches? These are the strongest candidates.
- Which functions have high scores in both searches? Likely the root cause.
- Any files that appear in symbol search but NOT semantic? Might be related but not the origin.
Present findings:
"Based on the symptom, I found these strong leads:
<file-path> contains <function-name> (appears in both semantic and symbol searches)
<other-file> has related code but lower confidence
- Total of X files mention this identifier
The issue likely originates in <strongest-candidate>. Want me to trace how code flows through this area?"
Branch based on findings:
- Clear origin found: Proceed to Step 3 (trace the call chain)
- Multiple candidates (3+): Ask user which area to focus on first
- Nothing relevant found: Try broader search terms:
- Remove specifics, search for general operation: "database connection" instead of "connect_to_postgres"
- Search for error handling patterns: "error handling", "exception catch"
- Search by file type: add
language="python" if language known
Step 3: Trace the Call Chain (Adaptive Depth)
Start shallow, go deeper on request. Default to ONE HOP first.
3a. Dependency Graph (Fast Path)
If the project has a dependency index, use the dependency MCP tools first — they provide instant, complete dependency data:
# What does this file depend on?
get_file_dependencies(file="<file-path>", depth=1)
# What depends on this file? (impact analysis)
get_file_impact(file="<file-path>", depth=2)
This immediately shows callers and callees at the file level. If the bug is in an imported dependency or caused by an upstream caller, the dependency tree reveals it directly.
If dependency tools return useful data: Use it as the primary trace and supplement with search below for symbol-level detail.
3b. Search-Based Tracing
One-hop trace:
- Find the suspected origin function:
search_code(
query="<function-name>",
symbol_name="<function-name>",
symbol_type="function",
smart_context=True
)
- Find immediate callers:
search_code(
query="calls <function-name>",
use_hybrid_search=True,
limit=10
)
- Find immediate callees (what it calls):
Look at the function body from step 1, extract called functions, then search for each:
search_code(
query="<called-function-name>",
symbol_name="<called-function-name>",
smart_context=True
)
Present one-hop view:
"Function <function-name> at <file>:<line>:
- Called by:
<caller-A>, <caller-B>, <caller-C>
- Calls:
<callee-D>, <callee-E>
Here's the function body:
[full function code from smart_context]
Checkpoint with user:
"This is one level deep. Want me to trace deeper into any of these callers or callees?"
If user wants deeper trace:
- Ask which direction: "Trace upward (who calls the callers) or downward (what the callees call)?"
- Repeat the one-hop search for the selected function
- Present the expanded view
- Repeat until root cause is identified or user says stop
Trace strategies:
- For errors in leaf functions: Trace UPWARD to find where bad data originates
- For errors in entry points: Trace DOWNWARD to find where the error is thrown
- For flow understanding: Trace both directions, building a call graph
Stop conditions:
- User says "that's enough"
- Found the root cause (code that's clearly wrong)
- Hit architectural boundaries (external API calls, database, file system)
Step 4: Root Cause Analysis
Present the root cause clearly:
-
What's wrong:
- Show the problematic code with full context (
smart_context=True)
- Explain why this code causes the symptom
- Highlight the specific line or logic error
-
Where it is:
- File path and line number
- Function/class containing the issue
- Context of surrounding code (what this function's purpose is)
-
Why it causes the symptom:
- Trace the logic: "When X happens, this code does Y, but should do Z"
- Connect back to user's original symptom
- Explain the propagation path if error bubbles up
Example root cause presentation:
"Root cause found in src/auth/validator.py:45 in function validate_token:
def validate_token(token: str) -> User:
decoded = jwt.decode(token, verify=False)
return User.from_dict(decoded)
The issue: verify=False disables signature verification, allowing any malformed JWT to pass. This causes the KeyError you saw because the fake token doesn't have required fields.
This explains your symptom: when an attacker sends a crafted JWT, it's accepted without validation, then fails when trying to extract user data."
Ask about fix suggestions (do NOT auto-suggest):
"Want me to suggest a fix based on how this is handled elsewhere in the codebase?"
If user wants fix suggestions:
- Search for correct patterns:
search_code(
query="JWT token validation with signature verification",
use_hybrid_search=True,
language="python"
)
- Find similar fixed code:
search_code(
query="jwt.decode verify signature",
use_hybrid_search=True
)
- Present fix based on established patterns:
"Here's how it's done correctly in
src/api/auth.py:23:
def validate_api_token(token: str) -> User:
try:
decoded = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
return User.from_dict(decoded)
except jwt.InvalidTokenError as e:
raise AuthenticationError(f"Invalid token: {e}")
Suggested fix for validator.py:45:
- Add signature verification with
SECRET_KEY
- Specify allowed algorithms
- Add exception handling for invalid tokens
Want me to show the exact code change?"
If user doesn't want fixes:
"Root cause identified. Let me know if you need anything else!"
Advanced Debugging Patterns
Pattern 1: Symbol type filtering for specific searches
When debugging object-oriented code:
# Find all classes related to authentication
search_code(query="authentication", symbol_type="class")
# Find all methods that handle errors
search_code(query="error handler", symbol_type=["method", "function"])
Pattern 2: Language filtering for polyglot codebases
When error is language-specific:
# Python-specific async issue
search_code(query="async await deadlock", language="python")
# TypeScript type error
search_code(query="type mismatch interface", language="typescript")
Pattern 3: Symbol name wildcards for related functions
When tracing naming conventions:
# Find all handler functions
search_code(query="request processing", symbol_name="*Handler")
# Find all validator methods
search_code(query="validation", symbol_name="validate*")
Pattern 4: Context expansion for full understanding
When you need complete function bodies:
# Get full function context (default with smart_context=True)
search_code(query="database transaction", smart_context=True)
# Get fixed context lines
search_code(query="error handling", context_before=10, context_after=10)
Pattern 5: Pipeline analysis for search debugging
When search results are unexpected, use analyze_query to see the full pipeline breakdown:
# See why a query returns specific results
analyze_query(query="getUserById")
Returns stage-by-stage diagnostics: identifier detection, hybrid mode decision, vector/keyword search results, RRF fusion breakdown (both/semantic-only/keyword-only counts), definition boost effects, and per-stage timings.
For installation instructions, see skills/README.md.