Skip to content

fix(postgresql): wire is_available + extract_params into PostgreSQL @tool decorators#703

Merged
Devesh36 merged 2 commits intoTracer-Cloud:mainfrom
ebrahim-sameh:fix/postgresql-tool-injection
Apr 20, 2026
Merged

fix(postgresql): wire is_available + extract_params into PostgreSQL @tool decorators#703
Devesh36 merged 2 commits intoTracer-Cloud:mainfrom
ebrahim-sameh:fix/postgresql-tool-injection

Conversation

@ebrahim-sameh
Copy link
Copy Markdown
Collaborator

References #702

Part 1 of 2. MySQL side ships in a separate PR opened alongside this one.

Summary

Adds the missing is_available and extract_params callbacks to the @tool(...) decorators on all 5 PostgreSQL diagnostic tools. Before this change, every one of them raised TypeError: missing 2 required positional arguments: 'host' and 'database' the moment the agent tried to invoke them. Mirrors the existing working pattern used by the MariaDB tool family at app/integrations/mariadb.py:160-176.

What changed

  • Added postgresql_is_available and postgresql_extract_params to app/integrations/postgresql.py.
  • Wired both helpers into the @tool(...) decorator of all 5 PostgreSQL tools (current queries, replication status, server status, slow queries, table stats).

PostgreSQL integration resolves credentials internally via resolve_postgresql_config (reading from the store/env), so extract_params only surfaces the three identifying params: host, database, port. Credentials stay out of tool signatures and are never seen by the LLM.

Scope: 6 files, +61 / -5 lines.

Evidence

Baseline (origin/main @ 6b02982, full RDS synthetic suite on Gemini 2.5-flash)

  • make test-rds-synthetic0/15 scenarios passed
  • Wall time: 838 s
  • 184 PostgreSQL TypeError failures during the run:
    • get_postgresql_server_status: 92
    • get_postgresql_current_queries: 40
    • get_postgresql_replication_status: 32
    • get_postgresql_table_stats: 20

After fix (this branch)

  • make test-rds-synthetic0 PostgreSQL TypeErrors (was 184)
  • Suite still shows 0/15 passing — RDS-specific diagnostic tooling over the CloudWatch / Performance Insights mock data is a separate gap. Failure reasons are now about real diagnostic quality (wrong category, missing keywords) rather than masked tool plumbing.
  • make test-cov → 2725 passed, 1 skipped, 10 warnings (no regressions)
  • ruff check on all changed files → clean
  • mypy on all changed files → clean

Test plan

  • make test-cov passes with no regressions
  • ruff check clean on all changed files
  • mypy clean on all changed files
  • Full make test-rds-synthetic run shows zero TypeError failures from PostgreSQL tools
  • MariaDB tools still work (not touched)

…tool decorators

All 5 PostgreSQL diagnostic tools previously raised
`TypeError: missing 2 required positional arguments: 'host' and 'database'`
whenever the agent tried to invoke them. The `@tool(...)` decorator on each
was missing the `is_available` and `extract_params` callbacks, so the tool
function got called with no kwargs even though `host` and `database` are
required.

Mirror the working MariaDB pattern from app/integrations/mariadb.py:
add `postgresql_is_available` and `postgresql_extract_params` to
app/integrations/postgresql.py, then wire both into all 5 PG tools'
decorators (current queries, replication status, server status, slow
queries, table stats).

PostgreSQL integration resolves credentials internally via
`resolve_postgresql_config`, so `extract_params` only surfaces the three
identifying params: host, database, port. Credentials stay out of tool
signatures and are never seen by the LLM.

Verified against the full RDS PostgreSQL synthetic suite
(make test-rds-synthetic) on Gemini 2.5-flash: PostgreSQL TypeError
failures drop from 184 to 0.
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 20, 2026

Greptile Summary

Adds postgresql_is_available and postgresql_extract_params callbacks to app/integrations/postgresql.py and wires them into all five PostgreSQL @tool decorators, exactly mirroring the working MariaDB pattern. The fix correctly omits credentials from extract_params since they are resolved internally by resolve_postgresql_config, ensuring they are never exposed to the LLM.

Confidence Score: 5/5

Safe to merge — minimal, well-scoped change that mirrors an established working pattern with no regressions reported.

All changes are additive and follow the existing MariaDB pattern precisely. The sources dict shape in detect_sources.py provides exactly the keys (host, database, port) that the new helpers consume, and credential handling is correctly delegated to resolve_postgresql_config. Test suite passes with zero new failures.

No files require special attention.

Important Files Changed

Filename Overview
app/integrations/postgresql.py Adds postgresql_is_available and postgresql_extract_params helpers, correctly mirroring the MariaDB pattern; credentials are intentionally excluded from extract_params in favour of resolve_postgresql_config.
app/tools/PostgreSQLCurrentQueriesTool/init.py Wires postgresql_is_available and postgresql_extract_params into the @tool decorator; imports added correctly and decorator arguments aligned with the function signature.
app/tools/PostgreSQLReplicationStatusTool/init.py Same two callbacks wired in; no issues.
app/tools/PostgreSQLServerStatusTool/init.py Same two callbacks wired in; no issues.
app/tools/PostgreSQLSlowQueriesTool/init.py Same two callbacks wired in; no issues.
app/tools/PostgreSQLTableStatsTool/init.py Same two callbacks wired in; no issues.

Sequence Diagram

sequenceDiagram
    participant Agent
    participant RegisteredTool
    participant postgresql_is_available
    participant postgresql_extract_params
    participant Tool Function
    participant resolve_postgresql_config
    participant PostgreSQL DB

    Agent->>RegisteredTool: plan action with available_sources
    RegisteredTool->>postgresql_is_available: is_available(sources)
    Note over postgresql_is_available: checks sources["postgresql"]["host"] && ["database"]
    postgresql_is_available-->>RegisteredTool: True/False
    alt is available
        RegisteredTool->>postgresql_extract_params: extract_params(sources)
        Note over postgresql_extract_params: returns {host, database, port} only — no credentials
        postgresql_extract_params-->>RegisteredTool: {host, database, port}
        RegisteredTool->>Tool Function: run(host, database, port)
        Tool Function->>resolve_postgresql_config: resolve(host, database, port)
        Note over resolve_postgresql_config: resolves credentials from store/env
        resolve_postgresql_config-->>Tool Function: PostgreSQLConfig (with credentials)
        Tool Function->>PostgreSQL DB: execute diagnostic query
        PostgreSQL DB-->>Tool Function: results
        Tool Function-->>Agent: diagnostic data
    else not available
        RegisteredTool-->>Agent: tool skipped
    end
Loading

Reviews (2): Last reviewed commit: "fix(postgresql): harden port coercion ag..." | Re-trigger Greptile

…ed integration

`dict.get("port", DEFAULT)` only substitutes the default when the key is
absent. If the resolved integration stores `{"port": None}` explicitly,
`pg.get("port", DEFAULT_POSTGRESQL_PORT)` returns None and `int(None)`
raises TypeError inside postgresql_extract_params.

Use `or` so both missing and None collapse to the default. Applied
preemptively — same finding Greptile raised on the MySQL side in Tracer-Cloud#704.
@ebrahim-sameh
Copy link
Copy Markdown
Collaborator Author

Pushed 3b5a13f to harden postgresql_extract_params: changed int(pg.get("port", DEFAULT_POSTGRESQL_PORT)) to int(pg.get("port") or DEFAULT_POSTGRESQL_PORT). This is a preemptive fix for the same edge case Greptile flagged on the MySQL side (#704) — dict.get(key, default) doesn't substitute the default when the key is present but the value is None, so int(None) would raise TypeError if a resolved integration stored {"port": null}. Applied here for pattern parity.

@Devesh36
Copy link
Copy Markdown
Collaborator

Heyyy this looks good

@Devesh36 Devesh36 merged commit 52e87f6 into Tracer-Cloud:main Apr 20, 2026
8 checks passed
@Devesh36
Copy link
Copy Markdown
Collaborator

Thanks for the contribution !!! 👍

@ebrahim-sameh
Copy link
Copy Markdown
Collaborator Author

@Devesh36 happy to contribute, and get things done.

yashksaini-coder pushed a commit that referenced this pull request Apr 20, 2026
… decorators (#707)

* fix(azure-sql): wire is_available + extract_params into Azure SQL @tool decorators

Same @tool decorator wiring bug the PostgreSQL and MySQL sides fixed in
#703 and #704, now for Azure SQL. All 5 Azure SQL diagnostic tools were
silently failing with TypeError: missing 2 required positional arguments
('server' and 'database') on every agent invocation, because the decorator
was missing the is_available and extract_params callbacks that inject
identifying params from resolved integrations.

Mirror the MariaDB/PostgreSQL/MySQL pattern: add azure_sql_is_available
and azure_sql_extract_params to app/integrations/azure_sql.py, then wire
both into all 5 Azure SQL tools (current queries, resource stats, server
status, slow queries, wait stats).

Azure SQL integration resolves credentials internally via
resolve_azure_sql_config, so extract_params only surfaces the three
identifying params (server, database, port). Credentials stay out of tool
signatures and are never seen by the LLM.

Applied Greptile's port-None hardening preemptively:
int(az.get("port") or DEFAULT_AZURE_SQL_PORT). Covers both missing and
explicit None port values in the resolved integration dict.

Added 14 unit tests covering azure_sql_is_available (6 cases) and
azure_sql_extract_params (8 cases including port-None, port-0,
port-as-string, and credential isolation).

Verified on make test-rds-synthetic (Gemini 2.5-flash): Azure SQL
TypeError failures drop from 92 to 0 across the 15 RDS scenarios.

* fix(azure-sql): harden server/database coercion against explicit None

Address Greptile review on #707: `str(None).strip()` produces the literal
string "None", not an empty string. If a stored integration has
`{"server": null}` or `{"database": null}`, az.get("server", "") returns
None (the default only applies when the key is absent), and
str(None).strip() yields "None" — a misleading non-empty string.

In practice is_available guards against this, but the extract_params
function can be called directly in tests or future code paths and
silently produce garbage. Mirror the `or` pattern already applied to
port, and match the AzureSQLConfig._normalize_server / _normalize_database
validators which use str(value or "").strip().

Added two unit tests covering the None case for both server and database.
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