Skip to content

Fix/explorer backend failure states - #980

Merged
KaifAhmad1 merged 8 commits into
mainfrom
fix/explorer-backend-failure-states
Aug 14, 2026
Merged

Fix/explorer backend failure states#980
KaifAhmad1 merged 8 commits into
mainfrom
fix/explorer-backend-failure-states

Conversation

@ZohaibHassan16

Copy link
Copy Markdown
Collaborator

Summary

Fixes #977.

This fixes a few Explorer UI issues around backend errors and search.

The main issue was that if the backend was down or returned an error, the graph page would just keep showing the loading/progress UI forever. The landing page also always showed "System Online" even when the backend could not be reached.

I also fixed a few smaller search UX issues I found while testing this.

Changes

Graph load error state + retry

useLoadGraph() already returned isError and error, but they were not being used.

Because retries are disabled (retry: 0), a failed graph request would leave the loading overlay stuck on the last progress state. There was no error message or retry option, so the only way to try again was refreshing the whole page.

GraphLoadingOverlay now takes error and onRetry.

When loading fails, it shows:

  • the actual fetch error message
  • a Retry button
  • retry without needing a full page reload

Landing page connection status

The landing page status was hardcoded to always show the green dot and "System Online".

I replaced the old ready boolean with three states:

  • checking
  • online
  • offline

The status dot, text and fourth metric card now all use the same state, so they should stay in sync.

When the backend cannot be reached, the page now shows "Backend Unreachable" instead of "System Online".

Search result dismiss + score display

The search results strip did not have a close button, so it stayed open and kept taking space from the graph until another search happened.

I added a small header with:

  • result count
  • dismiss button

I also changed relevance scores to display as whole numbers instead of values like 96.900 or 138.000.

Search typeahead

The graph search input only did something after submitting the form.

I added a debounced typeahead using the existing search endpoint.

Current behavior:

  • waits 250ms before searching
  • shows matching suggestions in a combobox
  • Up/Down arrows move through suggestions
  • Enter selects the highlighted suggestion
  • clicking a suggestion jumps to that node
  • Escape closes the dropdown
  • aria-activedescendant tracks the active result

Selecting a result also clears the current query.

Testing

I tested these against the running app:

  • Killed the backend -> graph workspace showed the error card with the real fetch error
  • Landing page changed to red "Backend Unreachable"
  • Restarted the backend and clicked Retry -> graph loaded again without refreshing the page
  • Landing page changed back to green "System Online"
  • Typeahead returned real backend results while typing
  • Arrow keys moved the selected suggestion correctly
  • aria-activedescendant updated with the selected suggestion
  • Clicking a suggestion focused the node and cleared the query
  • Escape closed the typeahead without clearing the query
  • Search result dismiss button removed the results strip and gave the canvas its full height again
  • Search scores displayed as whole numbers
  • npx tsc -b passes
  • npm run test:graph-store, test:graph-workspace and test:plugin-registry: 42/42 tests pass
  • npm run build succeeds
  • Explorer bundle structure is unchanged

The dependency-pre-bundle overlay had no failure path: on a fetch
error it kept rendering the last progress frame forever with no
retry. Route isError/error out of the load query, surface a real
error card with the underlying message, and let retry re-fetch
without a full page reload.
The status dot and 'System Online' text were static, so a dead
backend still looked healthy. Track checking/online/offline explicitly
and drive both off the same state so they can't disagree.
The results strip had no close affordance and stayed pinned until the
next search. Add a header row with a dismiss button, and round scores
to whole numbers instead of showing three decimals of a raw relevance
value nobody can act on.
Typing in the search box now debounces a query against the existing
search endpoint and shows a combobox dropdown, with arrow-key
navigation, Enter/click to jump straight to a node, and Escape to
dismiss. Previously nothing happened until the full form was
submitted.
@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Fix Explorer backend error states and improve graph search UX

🐞 Bug fix ✨ Enhancement 🕐 40+ Minutes

Grey Divider

AI Description

• Show a retryable error overlay when graph loading fails.
• Reflect real backend connectivity on the landing page status.
• Improve graph search with dismissible results, rounded scores, and typeahead suggestions.
Diagram

graph TD
  Welcome["WelcomeScreen (Landing)"] --> Api{{"Explorer API"}}
  GraphWS["GraphWorkspace"] --> SearchBar["SearchCommandBar"] --> Api
  GraphWS --> LoadHook["useLoadGraph()"] --> Api
  GraphWS --> Overlay["GraphLoadingOverlay"]
  Overlay -->|"onRetry()"| GraphWS
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use React Query for typeahead suggestions
  • ➕ Built-in dedupe/caching across identical queries
  • ➕ Standardized error/loading handling
  • ➕ Easier testability via query client
  • ➖ More plumbing (query keys, enabled conditions) for a lightweight UX feature
  • ➖ Care needed to avoid caching stale suggestions across rapid edits
2. Adopt a headless combobox library (e.g., Downshift/Reach UI)
  • ➕ Battle-tested ARIA combobox behavior and edge cases
  • ➕ Less custom keyboard/focus management code
  • ➖ Adds dependency + bundle weight
  • ➖ May constrain styling/behavior compared to custom UI
3. Centralize backend connectivity in a shared health hook/provider
  • ➕ Single source of truth for online/offline across landing + workspace
  • ➕ Enables global banners/toasts and consistent retry semantics
  • ➖ More architecture than needed if only used on the landing page
  • ➖ Requires deciding polling/backoff semantics

Recommendation: The PR’s approach is solid for the current scope: it makes graph-load failure explicit, adds an immediate retry path, and improves search UX without new dependencies. If typeahead and connectivity status expand to more screens, consider migrating those fetches into React Query (or a shared connectivity hook) to standardize caching and state management.

Files changed (3) +363 / -31

Enhancement (1) +231 / -18
GraphWorkspace.tsxSurface graph load failures; add search dismiss, rounded scores, and typeahead +231/-18

Surface graph load failures; add search dismiss, rounded scores, and typeahead

• Wires useLoadGraph() error/refetch into GraphLoadingOverlay so failures don’t leave the UI stuck and can be retried in-place. Adds a debounced combobox-style typeahead that calls the existing search endpoint with keyboard navigation and ARIA wiring. Improves search results with a dismiss control, clears errors on dismiss, and rounds relevance scores to whole numbers.

explorer/src/workspaces/GraphWorkspace/GraphWorkspace.tsx

Bug fix (2) +132 / -13
App.tsxMake landing status reflect real backend connectivity +37/-13

Make landing status reflect real backend connectivity

• Replaces the hardcoded "System Online" UI with an explicit checking/online/offline connection state. Updates landing styles to derive status color/shadows from a data-status attribute, and drives both the status label and metric card from the same state.

explorer/src/App.tsx

GraphLoadingOverlay.tsxAdd error + retry UI to the graph loading overlay +95/-0

Add error + retry UI to the graph loading overlay

• Extends GraphLoadingOverlay to accept an error message and optional retry callback. When present, renders an alert-style error card showing the underlying fetch error and a Retry button, with new CSS for error states.

explorer/src/workspaces/GraphWorkspace/GraphLoadingOverlay.tsx

@github-actions

Copy link
Copy Markdown

🔒 Security Scan Results

Safety — dependency vulnerabilities

✅ No findings.

Bandit — HIGH-severity code issues

✅ No findings.

Semgrep — static analysis patterns

Found 55.

Show all findings
  • python.lang.security.audit.non-literal-import.non-literal-import in semantica/__init__.py:58
  • python.lang.security.audit.dynamic-urllib-use-detected.dynamic-urllib-use-detected in semantica/cli.py:735
  • python.lang.security.audit.insecure-file-permissions.insecure-file-permissions in semantica/cli.py:3868
  • python.lang.security.audit.insecure-file-permissions.insecure-file-permissions in semantica/cli.py:4002
  • python.lang.security.audit.non-literal-import.non-literal-import in semantica/ingest/__init__.py:271
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/ingest/databricks_ingestor.py:506
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/databricks_ingestor.py:506
  • python.sqlalchemy.security.audit.avoid-sqlalchemy-text.avoid-sqlalchemy-text in semantica/ingest/db_ingestor.py:310
  • python.sqlalchemy.security.audit.avoid-sqlalchemy-text.avoid-sqlalchemy-text in semantica/ingest/db_ingestor.py:331
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/ingest/duckdb_ingestor.py:99
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/duckdb_ingestor.py:99
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/ingest/duckdb_ingestor.py:165
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/duckdb_ingestor.py:165
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/ingest/duckdb_ingestor.py:242
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/duckdb_ingestor.py:242
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/ingest/duckdb_ingestor.py:342
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/duckdb_ingestor.py:342
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/snowflake_ingestor.py:542
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/ingest/snowflake_ingestor.py:732
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/snowflake_ingestor.py:732
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/ingest/snowflake_ingestor.py:748
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/snowflake_ingestor.py:748
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/ingest/snowflake_ingestor.py:813
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/snowflake_ingestor.py:813
  • python.lang.security.audit.hardcoded-password-default-argument.hardcoded-password-default-argument in semantica/kg/graph_builder.py:1100
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/provenance/storage.py:506
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/provenance/storage.py:506
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/provenance/storage.py:509
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/provenance/storage.py:509
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/provenance/storage.py:512
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/provenance/storage.py:512
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/provenance/storage.py:513
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/provenance/storage.py:513
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/provenance/storage.py:575
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/provenance/storage.py:575
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/provenance/storage.py:828
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/provenance/storage.py:927
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/provenance/storage.py:950
  • python.lang.security.audit.non-literal-import.non-literal-import in semantica/semantic_extract/__init__.py:137
  • python.lang.security.audit.non-literal-import.non-literal-import in semantica/utils/helpers.py:508
  • python.lang.security.audit.non-literal-import.non-literal-import in semantica/utils/helpers.py:510
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/pgvector_store.py:498
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/pgvector_store.py:579
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/pgvector_store.py:618
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/pgvector_store.py:752
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/pgvector_store.py:900
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/pgvector_store.py:927
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/vector_store/sqlite_vec_store.py:240
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/sqlite_vec_store.py:240
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/sqlite_vec_store.py:311
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/sqlite_vec_store.py:402
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/sqlite_vec_store.py:454
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/sqlite_vec_store.py:689
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/vector_store/sqlite_vec_store.py:732
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/sqlite_vec_store.py:732

This security scan runs automatically on source-code PRs and bi-weekly (skipped for doc/markdown-only changes).

📊 Security Policy: CI fails on Safety vulnerabilities and Bandit HIGH-severity findings. Semgrep findings above are informational and do not block merge.

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Aug 14, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Stale typeahead suggestions ✓ Resolved 🐞 Bug ≡ Correctness
Description
SearchCommandBar’s typeahead can render suggestions for an older query because in-flight
/api/graph/search requests aren’t aborted when the input changes or is cleared, and late responses
still call setSuggestions/setSuggestionsOpen. It also ignores non-OK responses without clearing
existing suggestions, so stale results can persist when the backend errors.
Code

explorer/src/workspaces/GraphWorkspace/GraphWorkspace.tsx[R314-317]

+    const query = value.trim();
+    if (disabled || !query) {
+      setSuggestions([]);
+      setSuggestionsOpen(false);
Evidence
When the query is empty/disabled, the effect returns after clearing local state but never aborts an
already-started fetch, so a late response can reopen the dropdown. Separately, aborting only happens
inside the debounced callback (not on dependency change), and non-OK responses return null and are
ignored, leaving prior suggestions intact.

explorer/src/workspaces/GraphWorkspace/GraphWorkspace.tsx[309-320]
explorer/src/workspaces/GraphWorkspace/GraphWorkspace.tsx[322-345]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The typeahead suggestion fetch can update state with stale results because requests aren’t cancelled when the query changes/clears, and non-OK responses don’t clear existing suggestions.
## Issue Context
The debounced fetch only aborts the previous request when the next debounce callback fires, leaving a window where an older request can resolve and repopulate the dropdown after the user has typed a new query or cleared the input.
## Fix Focus Areas
- explorer/src/workspaces/GraphWorkspace/GraphWorkspace.tsx[309-352]
Suggested changes:
- Abort any in-flight request when `disabled || !query` (before returning).
- Abort any in-flight request in the effect cleanup when `[value, disabled]` changes (not just on unmount).
- On non-OK responses and non-abort errors, clear suggestions + close the dropdown (or otherwise ensure stale suggestions are not retained).
- Optionally guard `.then(...)` updates by verifying the response corresponds to the latest query (e.g., store the query/request id in a ref and compare before updating state).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can describe a rule in plain language on the Rules page and Qodo drafts it for you

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

…n error

Clearing the search box while a suggestion fetch was in flight never
aborted it, so a late response could reopen the dropdown with results
for a query that was no longer typed. A non-OK response also left
whatever suggestions were already on screen untouched instead of
clearing them. Abort on every effect cleanup (not just unmount) and
clear suggestions on any non-abort failure.
@ZohaibHassan16

Copy link
Copy Markdown
Collaborator Author

Fixed Qodo's finding

@github-actions

Copy link
Copy Markdown

🔒 Security Scan Results

Safety — dependency vulnerabilities

✅ No findings.

Bandit — HIGH-severity code issues

✅ No findings.

Semgrep — static analysis patterns

Found 55.

Show all findings
  • python.lang.security.audit.non-literal-import.non-literal-import in semantica/__init__.py:58
  • python.lang.security.audit.dynamic-urllib-use-detected.dynamic-urllib-use-detected in semantica/cli.py:735
  • python.lang.security.audit.insecure-file-permissions.insecure-file-permissions in semantica/cli.py:3868
  • python.lang.security.audit.insecure-file-permissions.insecure-file-permissions in semantica/cli.py:4002
  • python.lang.security.audit.non-literal-import.non-literal-import in semantica/ingest/__init__.py:271
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/ingest/databricks_ingestor.py:506
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/databricks_ingestor.py:506
  • python.sqlalchemy.security.audit.avoid-sqlalchemy-text.avoid-sqlalchemy-text in semantica/ingest/db_ingestor.py:310
  • python.sqlalchemy.security.audit.avoid-sqlalchemy-text.avoid-sqlalchemy-text in semantica/ingest/db_ingestor.py:331
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/ingest/duckdb_ingestor.py:99
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/duckdb_ingestor.py:99
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/ingest/duckdb_ingestor.py:165
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/duckdb_ingestor.py:165
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/ingest/duckdb_ingestor.py:242
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/duckdb_ingestor.py:242
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/ingest/duckdb_ingestor.py:342
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/duckdb_ingestor.py:342
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/snowflake_ingestor.py:542
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/ingest/snowflake_ingestor.py:732
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/snowflake_ingestor.py:732
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/ingest/snowflake_ingestor.py:748
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/snowflake_ingestor.py:748
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/ingest/snowflake_ingestor.py:813
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/snowflake_ingestor.py:813
  • python.lang.security.audit.hardcoded-password-default-argument.hardcoded-password-default-argument in semantica/kg/graph_builder.py:1100
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/provenance/storage.py:506
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/provenance/storage.py:506
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/provenance/storage.py:509
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/provenance/storage.py:509
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/provenance/storage.py:512
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/provenance/storage.py:512
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/provenance/storage.py:513
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/provenance/storage.py:513
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/provenance/storage.py:575
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/provenance/storage.py:575
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/provenance/storage.py:828
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/provenance/storage.py:927
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/provenance/storage.py:950
  • python.lang.security.audit.non-literal-import.non-literal-import in semantica/semantic_extract/__init__.py:137
  • python.lang.security.audit.non-literal-import.non-literal-import in semantica/utils/helpers.py:508
  • python.lang.security.audit.non-literal-import.non-literal-import in semantica/utils/helpers.py:510
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/pgvector_store.py:498
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/pgvector_store.py:579
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/pgvector_store.py:618
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/pgvector_store.py:752
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/pgvector_store.py:900
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/pgvector_store.py:927
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/vector_store/sqlite_vec_store.py:240
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/sqlite_vec_store.py:240
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/sqlite_vec_store.py:311
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/sqlite_vec_store.py:402
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/sqlite_vec_store.py:454
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/sqlite_vec_store.py:689
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/vector_store/sqlite_vec_store.py:732
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/sqlite_vec_store.py:732

This security scan runs automatically on source-code PRs and bi-weekly (skipped for doc/markdown-only changes).

📊 Security Policy: CI fails on Safety vulnerabilities and Bandit HIGH-severity findings. Semgrep findings above are informational and do not block merge.

@github-actions

Copy link
Copy Markdown

🔒 Security Scan Results

Safety — dependency vulnerabilities

✅ No findings.

Bandit — HIGH-severity code issues

✅ No findings.

Semgrep — static analysis patterns

Found 55.

Show all findings
  • python.lang.security.audit.non-literal-import.non-literal-import in semantica/__init__.py:58
  • python.lang.security.audit.dynamic-urllib-use-detected.dynamic-urllib-use-detected in semantica/cli.py:735
  • python.lang.security.audit.insecure-file-permissions.insecure-file-permissions in semantica/cli.py:3868
  • python.lang.security.audit.insecure-file-permissions.insecure-file-permissions in semantica/cli.py:4002
  • python.lang.security.audit.non-literal-import.non-literal-import in semantica/ingest/__init__.py:271
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/ingest/databricks_ingestor.py:506
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/databricks_ingestor.py:506
  • python.sqlalchemy.security.audit.avoid-sqlalchemy-text.avoid-sqlalchemy-text in semantica/ingest/db_ingestor.py:310
  • python.sqlalchemy.security.audit.avoid-sqlalchemy-text.avoid-sqlalchemy-text in semantica/ingest/db_ingestor.py:331
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/ingest/duckdb_ingestor.py:99
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/duckdb_ingestor.py:99
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/ingest/duckdb_ingestor.py:165
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/duckdb_ingestor.py:165
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/ingest/duckdb_ingestor.py:242
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/duckdb_ingestor.py:242
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/ingest/duckdb_ingestor.py:342
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/duckdb_ingestor.py:342
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/snowflake_ingestor.py:542
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/ingest/snowflake_ingestor.py:732
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/snowflake_ingestor.py:732
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/ingest/snowflake_ingestor.py:748
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/snowflake_ingestor.py:748
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/ingest/snowflake_ingestor.py:813
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/snowflake_ingestor.py:813
  • python.lang.security.audit.hardcoded-password-default-argument.hardcoded-password-default-argument in semantica/kg/graph_builder.py:1181
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/provenance/storage.py:506
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/provenance/storage.py:506
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/provenance/storage.py:509
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/provenance/storage.py:509
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/provenance/storage.py:512
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/provenance/storage.py:512
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/provenance/storage.py:513
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/provenance/storage.py:513
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/provenance/storage.py:575
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/provenance/storage.py:575
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/provenance/storage.py:828
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/provenance/storage.py:927
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/provenance/storage.py:950
  • python.lang.security.audit.non-literal-import.non-literal-import in semantica/semantic_extract/__init__.py:137
  • python.lang.security.audit.non-literal-import.non-literal-import in semantica/utils/helpers.py:508
  • python.lang.security.audit.non-literal-import.non-literal-import in semantica/utils/helpers.py:510
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/pgvector_store.py:498
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/pgvector_store.py:579
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/pgvector_store.py:618
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/pgvector_store.py:752
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/pgvector_store.py:900
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/pgvector_store.py:927
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/vector_store/sqlite_vec_store.py:240
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/sqlite_vec_store.py:240
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/sqlite_vec_store.py:311
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/sqlite_vec_store.py:402
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/sqlite_vec_store.py:454
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/sqlite_vec_store.py:689
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/vector_store/sqlite_vec_store.py:732
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/sqlite_vec_store.py:732

This security scan runs automatically on source-code PRs and bi-weekly (skipped for doc/markdown-only changes).

📊 Security Policy: CI fails on Safety vulnerabilities and Bandit HIGH-severity findings. Semgrep findings above are informational and do not block merge.

@Sameer6305 Sameer6305 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hi @ZohaibHassan16, verified the fix mechanism directly against the code, it's solid.

useLoadGraph() genuinely returns real isError/error/refetch from React Query, showLoadingOverlay correctly resolves once isError is true even with stale loadingProgress sitting around, Retry calls a real refetch() (not just a UI reset), and the landing page's checking/online/offline state is driven by an actual fetch outcome with no stuck-state path and no drift between the status dot and the 4th metric card. Good work on all of that.

One thing needs addressing before merge, though it's the root cause the issue itself called out, not just a symptom:

GraphWorkspaceShell.tsx already had a working isError/error implementation, and it's still dead code after this PR. It's not imported anywhere (confirmed — no file imports it, App.tsx loads GraphWorkspace directly). This PR adds the fix as a third, separate implementation in GraphWorkspace.tsx instead. So after this merges, we'd have three places with overlapping graph-loading/error-handling logic — GraphWorkspaceShell.tsx (dead), the old GraphWorkspace.tsx logic being replaced, and the new one — rather than fewer. The issue named "two copies that drifted apart" as the actual reason this bug slipped through in the first place, so leaving a third copy around undermines the fix.

Could you either:

  1. Delete GraphWorkspaceShell.tsx if it's genuinely unused/superseded, or
  2. If it's meant to be used somewhere (a route or feature flag I'm missing?), wire it up and reuse the same error-handling logic instead of duplicating it.

Either is fine, just want to avoid us being back here again in a few months with a fourth copy.

Small, non-blocking note: graphLoadError is typed Error | null in GraphWorkspace.tsx, but React Query actually returns it as unknown, the instanceof check handles it safely at runtime, so this isn't a bug, just worth tightening the type annotation while you're in there.

Requesting changes on the dead-code question, everything else here looks ready to go once that's resolved.

@ZohaibHassan16

Copy link
Copy Markdown
Collaborator Author

Deliberately leaving the GraphWorkspaceShell.tsx deletion out of this PR, see #981 . Makes it easier to track and test.

@Sameer6305
Sameer6305 dismissed their stale review August 14, 2026 11:27

Zohaib cleared it out.
Withdrawing my earlier request-changes approving.

@ZohaibHassan16

Copy link
Copy Markdown
Collaborator Author

Also graphLoadError type is already correct as-is.

useLoadGraph.ts calls useQuery<GraphLoadSummary>(...) with only the first generic provided. In TanStack Query v5, the second generic (TError) defaults to Error and I couldn't find any Register override in the app changing that.

So error is actually typed as Error | null, not unknown.

I also checked it with TypeScript instead of only relying on the docs:

const asError: Error | null = error;   // works
const asString: string = error;        // TS2322

The compiler error names the type directly, so that confirms it too.

I’d still keep the instanceof Error check, just for a different reason. TypeScript types aren’t enforced at runtime, so a query function could still reject with something that isn’t actually an Error.

But there’s no unknownError | null cleanup to make here. It’s already typed as Error | null. Thank you for the thorough review.

@Sameer6305

Copy link
Copy Markdown
Collaborator

@KaifAhmad1 approved from my side.

KaifAhmad1
KaifAhmad1 previously approved these changes Aug 14, 2026

@KaifAhmad1 KaifAhmad1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Verified independently (worktree + `npm ci`), not just reading the description:

  • Confirmed in `useLoadGraph.ts` that `useQuery` returns the full result (isError/error/refetch all real) with `retry: 0`, and the uncaught `fetch()` inside `queryFn` genuinely propagates a backend-down failure instead of being swallowed — matches the root cause in #977.
  • `npx tsc -b`: clean.
  • `test:graph-store` / `test:graph-workspace` / `test:plugin-registry`: 42/42 passed.
  • `npm run build`: succeeds.
  • Traced the stale-suggestion abort fix (`AbortController` in the typeahead effect) — correctly cancels in-flight requests on re-debounce and on selection-clear.

Thanks @ZohaibHassan16 for the thorough writeup and testing notes, and @Sameer6305 for catching the dead-code question early and getting it properly scoped out to #981 instead of blocking here. Approving for merge.

Documents the (#980, closes #977) fix in the Unreleased/Fixed section.
KaifAhmad1
KaifAhmad1 previously approved these changes Aug 14, 2026

@KaifAhmad1 KaifAhmad1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Re-approving after the changelog-only follow-up commit — no code changed, review above still stands.

@KaifAhmad1 KaifAhmad1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Re-approving after merging main to resolve the CHANGELOG.md conflict with #932 — no functional code touched, review above still stands.

@github-actions

Copy link
Copy Markdown

🔒 Security Scan Results

Safety — dependency vulnerabilities

✅ No findings.

Bandit — HIGH-severity code issues

✅ No findings.

Semgrep — static analysis patterns

Found 55.

Show all findings
  • python.lang.security.audit.non-literal-import.non-literal-import in semantica/__init__.py:58
  • python.lang.security.audit.dynamic-urllib-use-detected.dynamic-urllib-use-detected in semantica/cli.py:735
  • python.lang.security.audit.insecure-file-permissions.insecure-file-permissions in semantica/cli.py:3868
  • python.lang.security.audit.insecure-file-permissions.insecure-file-permissions in semantica/cli.py:4002
  • python.lang.security.audit.non-literal-import.non-literal-import in semantica/ingest/__init__.py:271
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/ingest/databricks_ingestor.py:506
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/databricks_ingestor.py:506
  • python.sqlalchemy.security.audit.avoid-sqlalchemy-text.avoid-sqlalchemy-text in semantica/ingest/db_ingestor.py:310
  • python.sqlalchemy.security.audit.avoid-sqlalchemy-text.avoid-sqlalchemy-text in semantica/ingest/db_ingestor.py:331
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/ingest/duckdb_ingestor.py:99
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/duckdb_ingestor.py:99
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/ingest/duckdb_ingestor.py:165
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/duckdb_ingestor.py:165
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/ingest/duckdb_ingestor.py:242
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/duckdb_ingestor.py:242
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/ingest/duckdb_ingestor.py:342
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/duckdb_ingestor.py:342
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/snowflake_ingestor.py:542
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/ingest/snowflake_ingestor.py:732
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/snowflake_ingestor.py:732
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/ingest/snowflake_ingestor.py:748
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/snowflake_ingestor.py:748
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/ingest/snowflake_ingestor.py:813
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/ingest/snowflake_ingestor.py:813
  • python.lang.security.audit.hardcoded-password-default-argument.hardcoded-password-default-argument in semantica/kg/graph_builder.py:1181
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/provenance/storage.py:506
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/provenance/storage.py:506
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/provenance/storage.py:509
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/provenance/storage.py:509
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/provenance/storage.py:512
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/provenance/storage.py:512
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/provenance/storage.py:513
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/provenance/storage.py:513
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/provenance/storage.py:575
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/provenance/storage.py:575
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/provenance/storage.py:828
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/provenance/storage.py:927
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/provenance/storage.py:950
  • python.lang.security.audit.non-literal-import.non-literal-import in semantica/semantic_extract/__init__.py:137
  • python.lang.security.audit.non-literal-import.non-literal-import in semantica/utils/helpers.py:508
  • python.lang.security.audit.non-literal-import.non-literal-import in semantica/utils/helpers.py:510
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/pgvector_store.py:498
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/pgvector_store.py:579
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/pgvector_store.py:618
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/pgvector_store.py:752
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/pgvector_store.py:900
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/pgvector_store.py:927
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/vector_store/sqlite_vec_store.py:240
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/sqlite_vec_store.py:240
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/sqlite_vec_store.py:311
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/sqlite_vec_store.py:402
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/sqlite_vec_store.py:454
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/sqlite_vec_store.py:689
  • python.lang.security.audit.formatted-sql-query.formatted-sql-query in semantica/vector_store/sqlite_vec_store.py:732
  • python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query in semantica/vector_store/sqlite_vec_store.py:732

This security scan runs automatically on source-code PRs and bi-weekly (skipped for doc/markdown-only changes).

📊 Security Policy: CI fails on Safety vulnerabilities and Bandit HIGH-severity findings. Semgrep findings above are informational and do not block merge.

@KaifAhmad1
KaifAhmad1 merged commit 75f88b1 into main Aug 14, 2026
15 checks passed
@KaifAhmad1
KaifAhmad1 deleted the fix/explorer-backend-failure-states branch August 14, 2026 13:53
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.

Explorer UI doesn't handle backend being down: graph load just hangs forever

3 participants