Skip to content

fix(builder): Auto-cleanup invalid/orphan edges during graph operations - #12113

Closed
Otto-AGPT wants to merge 6 commits into
devfrom
otto/secrt-1959-fix-graph-edge-desync
Closed

fix(builder): Auto-cleanup invalid/orphan edges during graph operations#12113
Otto-AGPT wants to merge 6 commits into
devfrom
otto/secrt-1959-fix-graph-edge-desync

Conversation

@Otto-AGPT

@Otto-AGPT Otto-AGPT commented Feb 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes graph desync issues where edge deletions don't persist, causing stale connections that users cannot remove. The fix auto-removes invalid edges at multiple points to ensure graphs stay clean.

Problem

Edge deletion in the Agent Builder is not persisting correctly:

  • When users delete edges between nodes, the deletion sometimes doesn't stick
  • This causes stale/orphan node connections across builds that cannot be corrected
  • Users have no way to remove these invalid edges from the UI

Solution

Frontend Changes

edgeStore.ts:

  • addLinks(): Validate links during load - skip edges referencing non-existent nodes
  • getBackendLinks(): Filter invalid edges before sending to backend during save

useSaveGraph.ts:

  • After successful save, sync edge store with authoritative backend state
  • This prevents desync between frontend and backend

Backend Changes

graph.py:

  • Add prune_invalid_links() method that removes:
    • Links referencing non-existent nodes
    • Links with invalid block IDs
  • Called during _validate_graph_structure() to auto-cleanup orphan edges before save

Testing

  1. Create agent with multiple nodes and edges
  2. Delete a node (edges should auto-cleanup)
  3. Save and reload - no orphan edges
  4. Delete an edge directly, save, reload - edge stays deleted

Related Issues

Closes SECRT-1959

Greptile Overview

Greptile Summary

This PR implements comprehensive edge cleanup to fix persistent desync issues where deleted edges would reappear after save/reload. The fix adds validation at three key points:

Backend (graph.py):

  • Added prune_invalid_links() method to remove orphan edges referencing non-existent nodes or blocks
  • Auto-cleanup runs during graph validation before save

Frontend (edgeStore.ts):

  • getBackendLinks() now filters out invalid edges before sending to backend
  • addLinks() validates links during load and skips edges with missing nodes

Sync Logic (useSaveGraph.ts):

  • After successful save, syncs edge store with authoritative backend state using syncEdgesWithBackend()
  • Prevents frontend/backend desync by replacing local edges with backend-approved edges

The changes implement defense-in-depth by validating at multiple layers (frontend pre-save, backend validation, frontend post-save sync, frontend load). This ensures orphan edges are caught and removed regardless of where they originate.

Confidence Score: 4/5

  • This PR is safe to merge with minimal risk - addresses a real UX issue with a defensive multi-layer approach
  • The implementation is sound with validation at multiple layers (frontend filter, backend pruning, post-save sync). The changes are focused and don't introduce breaking changes. One minor documentation issue was found (docstring mentions pin validation that isn't implemented), but this doesn't affect functionality. No tests were added for the new prune_invalid_links() method, though the existing validation flow has test coverage.
  • No files require special attention - all changes are straightforward defensive checks

Sequence Diagram

sequenceDiagram
    participant User
    participant Frontend
    participant edgeStore
    participant useSaveGraph
    participant Backend
    participant graph.py

    User->>Frontend: Delete node/edge
    Frontend->>edgeStore: Update local state
    
    User->>Frontend: Click Save
    Frontend->>useSaveGraph: saveGraph()
    useSaveGraph->>edgeStore: getBackendLinks()
    edgeStore->>edgeStore: Filter invalid edges<br/>(check node existence)
    edgeStore-->>useSaveGraph: Valid links only
    
    useSaveGraph->>Backend: POST/PUT graph with links
    Backend->>graph.py: _validate_graph_structure()
    graph.py->>graph.py: prune_invalid_links()<br/>(remove orphan edges)
    graph.py->>graph.py: Validate remaining links
    Backend-->>useSaveGraph: Return cleaned graph
    
    useSaveGraph->>useSaveGraph: syncEdgesWithBackend()
    useSaveGraph->>edgeStore: setEdges(backend links)
    edgeStore->>edgeStore: Replace all edges
    
    User->>Frontend: Reload page
    Frontend->>Backend: GET graph
    Backend-->>Frontend: Return graph with links
    Frontend->>edgeStore: addLinks(links)
    edgeStore->>edgeStore: Filter invalid links<br/>(check node existence)
    edgeStore->>edgeStore: Add valid edges only
Loading

Last reviewed commit: 6cb794c

@Otto-AGPT
Otto-AGPT requested a review from a team as a code owner February 14, 2026 12:42
@Otto-AGPT
Otto-AGPT requested review from Bentlybro and Swiftyos and removed request for a team February 14, 2026 12:42
@github-project-automation github-project-automation Bot moved this to 🆕 Needs initial review in AutoGPT development kanban Feb 14, 2026
@github-actions github-actions Bot added platform/frontend AutoGPT Platform - Front end platform/backend AutoGPT Platform - Back end labels Feb 14, 2026
@coderabbitai

coderabbitai Bot commented Feb 14, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Backend now prunes invalid/orphan links during graph validation; frontend syncs its edge store to the backend's authoritative links after save and validates/inserts links only when their nodes exist.

Changes

Cohort / File(s) Summary
Backend Link Pruning
autogpt_platform/backend/backend/data/graph.py
Added GraphModel.prune_invalid_links(graph: BaseGraph) -> int to remove links whose source/sink nodes or blocks are missing/invalid; invoked at start of _validate_graph_structure. Logs warnings and returns count pruned.
Frontend Save Hook
autogpt_platform/frontend/src/app/(platform)/build/hooks/useSaveGraph.ts
Added syncEdgesWithBackend() (uses linkToCustomEdge) and calls it after successful create/update so frontend edge state matches backend-authoritative links.
Frontend Edge Store Validation
autogpt_platform/frontend/src/app/(platform)/build/stores/edgeStore.ts
getBackendLinks() filters out edges with missing source/target nodes (warns). addLinks() validates incoming links, skips/or warns on orphan links, and batches valid additions to push history once.
Flow initialization sequencing
autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/useFlow.ts
Consolidated node+edge initialization: nodes are added/reset first, edges cleared and then added conditionally when graph?.links exists to avoid node/edge race conditions.

Sequence Diagram

sequenceDiagram
    actor User
    participant Frontend
    participant Backend
    participant EdgeStore

    User->>Frontend: Save graph (create/update)
    Frontend->>Backend: Send graph payload (nodes, links)
    Backend->>Backend: _validate_graph_structure()
    Backend->>Backend: prune_invalid_links(graph)
    Backend-->>Frontend: Return saved graph with authoritative links
    Frontend->>EdgeStore: syncEdgesWithBackend(links)
    EdgeStore->>EdgeStore: map links -> edges, filter invalid/orphan
    EdgeStore-->>Frontend: Edge store updated
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

Review effort 2/5

Suggested reviewers

  • Bentlybro
  • 0ubbe

Poem

🐰 I hopped through nodes both near and far,

Nibbled orphan links beneath the star,
Backend trimmed what should not belong,
Frontend listened and synced along,
Now edges hum a tidy song.

🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(builder): Auto-cleanup invalid/orphan edges during graph operations' clearly and concisely summarizes the main change of automatically removing stale edges during graph save/reload cycles.
Description check ✅ Passed The description comprehensively explains the problem (edge deletions not persisting), solution (multi-layer validation across frontend and backend), and testing approach related to the changeset.
Linked Issues check ✅ Passed The PR fully addresses the objectives in SECRT-1959: frontend validates/filters invalid edges during load and save; backend prunes orphan links during graph validation; post-save sync restores authoritative state.
Out of Scope Changes check ✅ Passed All code changes focus on edge/link validation and cleanup: backend pruning, frontend filtering, and post-save sync. The consolidation of node/edge initialization in useFlow.ts is directly related to preventing race conditions that could leave stale edges.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into dev

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch otto/secrt-1959-fix-graph-edge-desync

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Feb 14, 2026

Copy link
Copy Markdown
Contributor

🔍 PR Overlap Detection

This check compares your PR against all other open PRs targeting the same branch to detect potential merge conflicts early.

🟢 Low Risk — File Overlap Only

These PRs touch the same files but different sections (click to expand)

Summary: 0 conflict(s), 0 medium risk, 3 low risk (out of 3 PRs with file overlap)


Auto-generated on push. Ignores: openapi.json, lock files.

@greptile-apps greptile-apps Bot 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.

3 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment thread autogpt_platform/backend/backend/data/graph.py Outdated

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@autogpt_platform/backend/backend/data/graph.py`:
- Around line 870-922: The docstring for prune_invalid_links mentions pruning
links with invalid pin names but the function never checks pin names; either
remove that bullet or implement pin validation here: inside prune_invalid_links
(not in _validate_graph_structure) after obtaining source_block and sink_block,
verify that link.source_pin exists on source_block and link.sink_pin exists on
sink_block (use the block's pin/port descriptors or methods the codebase
provides for pin lookup), log a warning and skip the link if either pin is
missing, then append valid links as before; ensure warnings reference
link.source_id/sink_id and link.source_pin/link.sink_pin so diagnostics match
existing logs.
🧹 Nitpick comments (4)
autogpt_platform/frontend/src/app/(platform)/build/stores/edgeStore.ts (1)

138-151: Consider that addEdge pushes a history entry per link — bulk loads may flood the undo stack.

Each call to get().addEdge(...) inside the loop (Line 150) records an independent history snapshot via useHistoryStore.getState().pushState(prevState). When loading a graph with many links, this produces one undo entry per edge, which is likely unintended.

This is pre-existing behavior and not introduced by this PR, but the new validation logic in addLinks makes this a good time to note it. A batch approach (collect valid edges, then upsertMany + single history push) would be cleaner.

autogpt_platform/frontend/src/app/(platform)/build/hooks/useSaveGraph.ts (1)

25-28: JSDoc comment could be trimmed.

The coding guidelines state to avoid comments unless the code is very complex. The function name syncEdgesWithBackend is already self-documenting. Consider removing or minimizing the JSDoc. As per coding guidelines: "Avoid comments at all times unless the code is very complex."

autogpt_platform/backend/backend/data/graph.py (2)

898-912: get_block() instantiates a new block object per call — consider caching within this method.

get_block(block_id) calls cls() each time (per the relevant snippet from blocks/__init__.py). If multiple links share the same source/sink nodes, this creates redundant block instances. You could reuse the node_map to cache block lookups.

♻️ Cache block lookups
     node_map = {v.id: v for v in graph.nodes}
+    block_cache: dict[str, "AnyBlockSchema | None"] = {}
     original_count = len(graph.links)
     valid_links = []

     for link in graph.links:
         source_node = node_map.get(link.source_id)
         sink_node = node_map.get(link.sink_id)

         # Skip if either node doesn't exist
         if not source_node or not sink_node:
             logger.warning(...)
             continue

         # Skip if source block doesn't exist
-        source_block = get_block(source_node.block_id)
+        if source_node.block_id not in block_cache:
+            block_cache[source_node.block_id] = get_block(source_node.block_id)
+        source_block = block_cache[source_node.block_id]
         if not source_block:
             logger.warning(...)
             continue

         # Skip if sink block doesn't exist
-        sink_block = get_block(sink_node.block_id)
+        if sink_node.block_id not in block_cache:
+            block_cache[sink_node.block_id] = get_block(sink_node.block_id)
+        sink_block = block_cache[sink_node.block_id]
         if not sink_block:
             logger.warning(...)
             continue

924-928: Validation now has a mutation side-effect — document this for callers.

_validate_graph_structure is a validation method, but it now mutates graph.links via prune_invalid_links. This is intentional per the PR objectives, but callers like fork_graph (Line 1536) might not expect validation to alter the graph. Consider adding a brief note in the validate_graph docstring (Line 674) to make this side-effect explicit.

📝 Suggested docstring update
     def validate_graph(
         self,
         for_run: bool = False,
         nodes_input_masks: Optional["NodesInputMasks"] = None,
     ):
         """
         Validate graph structure and raise `ValueError` on issues.
         For structured error reporting, use `validate_graph_get_errors`.
+
+        Note: This method has side effects — it prunes invalid/orphan links
+        from the graph as part of structural validation.
         """
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b8f5c20 and 6cb794c.

📒 Files selected for processing (3)
  • autogpt_platform/backend/backend/data/graph.py
  • autogpt_platform/frontend/src/app/(platform)/build/hooks/useSaveGraph.ts
  • autogpt_platform/frontend/src/app/(platform)/build/stores/edgeStore.ts
🧰 Additional context used
📓 Path-based instructions (15)
autogpt_platform/frontend/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

autogpt_platform/frontend/**/*.{ts,tsx,js,jsx}: Use Node.js 21+ with pnpm package manager for frontend development
Always run 'pnpm format' for formatting and linting code in frontend development

Files:

  • autogpt_platform/frontend/src/app/(platform)/build/hooks/useSaveGraph.ts
  • autogpt_platform/frontend/src/app/(platform)/build/stores/edgeStore.ts
autogpt_platform/frontend/**/*.{tsx,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

autogpt_platform/frontend/**/*.{tsx,ts}: Use function declarations for components and handlers (not arrow functions) in React components
Only use arrow functions for small inline lambdas (map, filter, etc.) in React components
Use PascalCase for component names and camelCase with 'use' prefix for hook names in React
Use Tailwind CSS utilities only for styling in frontend components
Use design system components from 'src/components/' (atoms, molecules, organisms) in frontend development
Never use 'src/components/legacy/' in frontend code
Only use Phosphor Icons (@phosphor-icons/react) for icons in frontend components
Use generated API hooks from '@/app/api/generated/endpoints/' instead of deprecated 'BackendAPI' or 'src/lib/autogpt-server-api/
'
Use React Query for server state (via generated hooks) in frontend development
Default to client components ('use client') in Next.js; only use server components for SEO or extreme TTFB needs
Use '' component for rendering errors in frontend UI; use toast notifications for mutation errors; use 'Sentry.captureException()' for manual exceptions
Separate render logic from data/behavior in React components; keep comments minimal (code should be self-documenting)

Files:

  • autogpt_platform/frontend/src/app/(platform)/build/hooks/useSaveGraph.ts
  • autogpt_platform/frontend/src/app/(platform)/build/stores/edgeStore.ts
autogpt_platform/frontend/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

autogpt_platform/frontend/**/*.{ts,tsx}: No barrel files or 'index.ts' re-exports in frontend code
Regenerate API hooks with 'pnpm generate:api' after backend OpenAPI spec changes in frontend development

Files:

  • autogpt_platform/frontend/src/app/(platform)/build/hooks/useSaveGraph.ts
  • autogpt_platform/frontend/src/app/(platform)/build/stores/edgeStore.ts
autogpt_platform/frontend/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (autogpt_platform/frontend/CLAUDE.md)

autogpt_platform/frontend/src/**/*.{ts,tsx}: Fully capitalize acronyms in symbols, e.g. graphID, useBackendAPI
Use function declarations (not arrow functions) for components and handlers
Separate render logic (.tsx) from business logic (use*.ts hooks)
Use shadcn/ui (Radix UI primitives) with Tailwind CSS styling for UI components
Use Phosphor Icons only for icons
Use ErrorCard for render errors, toast for mutations, and Sentry for exceptions
Use design system components from src/components/ (atoms, molecules, organisms)
Never use src/components/__legacy__/* components
Use generated API hooks from @/app/api/__generated__/endpoints/ with pattern use{Method}{Version}{OperationName}
Use Tailwind CSS only for styling, with design tokens
Do not use useCallback or useMemo unless asked to optimize a given function
Never type with any unless a variable/attribute can ACTUALLY be of any type

autogpt_platform/frontend/src/**/*.{ts,tsx}: Structure components as ComponentName/ComponentName.tsx + useComponentName.ts + helpers.ts and use design system components from src/components/ (atoms, molecules, organisms)
Use generated API hooks from @/app/api/__generated__/endpoints/ with pattern use{Method}{Version}{OperationName} and regenerate with pnpm generate:api
Use function declarations (not arrow functions) for components and handlers
Separate render logic from business logic with component.tsx + useComponent.ts + helpers.ts structure
Colocate state when possible, avoid creating large components, use sub-components in local /components folder
Avoid large hooks, abstract logic into helpers.ts files when sensible
Use arrow functions only for callbacks, not for component declarations
Avoid comments at all times unless the code is very complex
Do not use useCallback or useMemo unless asked to optimize a given function

Files:

  • autogpt_platform/frontend/src/app/(platform)/build/hooks/useSaveGraph.ts
  • autogpt_platform/frontend/src/app/(platform)/build/stores/edgeStore.ts
autogpt_platform/frontend/src/**/*use*.ts

📄 CodeRabbit inference engine (autogpt_platform/frontend/CLAUDE.md)

Do not type hook returns, let TypeScript infer as much as possible

Files:

  • autogpt_platform/frontend/src/app/(platform)/build/hooks/useSaveGraph.ts
autogpt_platform/frontend/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

autogpt_platform/frontend/**/*.{js,jsx,ts,tsx}: Format frontend code using pnpm format
Never use components from src/components/__legacy__/*

Files:

  • autogpt_platform/frontend/src/app/(platform)/build/hooks/useSaveGraph.ts
  • autogpt_platform/frontend/src/app/(platform)/build/stores/edgeStore.ts
autogpt_platform/frontend/**/*.{js,jsx,ts,tsx,css}

📄 CodeRabbit inference engine (AGENTS.md)

Use Tailwind CSS only for styling, use design tokens, and use Phosphor Icons only

Files:

  • autogpt_platform/frontend/src/app/(platform)/build/hooks/useSaveGraph.ts
  • autogpt_platform/frontend/src/app/(platform)/build/stores/edgeStore.ts
autogpt_platform/frontend/src/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Do not type hook returns, let Typescript infer as much as possible

Files:

  • autogpt_platform/frontend/src/app/(platform)/build/hooks/useSaveGraph.ts
  • autogpt_platform/frontend/src/app/(platform)/build/stores/edgeStore.ts
autogpt_platform/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Never type with any, if no types available use unknown

Files:

  • autogpt_platform/frontend/src/app/(platform)/build/hooks/useSaveGraph.ts
  • autogpt_platform/frontend/src/app/(platform)/build/stores/edgeStore.ts
autogpt_platform/backend/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

autogpt_platform/backend/**/*.py: Use Python 3.11 (required; managed by Poetry via pyproject.toml) for backend development
Always run 'poetry run format' (Black + isort) before linting in backend development
Always run 'poetry run lint' (ruff) after formatting in backend development

Files:

  • autogpt_platform/backend/backend/data/graph.py
autogpt_platform/backend/backend/data/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

All data access in backend requires user ID checks; verify this for any 'data/*.py' changes

Files:

  • autogpt_platform/backend/backend/data/graph.py
autogpt_platform/backend/**/*.{py,txt}

📄 CodeRabbit inference engine (autogpt_platform/backend/CLAUDE.md)

Use poetry run prefix for all Python commands, including testing, linting, formatting, and migrations

Files:

  • autogpt_platform/backend/backend/data/graph.py
autogpt_platform/backend/backend/**/*.py

📄 CodeRabbit inference engine (autogpt_platform/backend/CLAUDE.md)

Use Prisma ORM for database operations in PostgreSQL with pgvector for embeddings

Files:

  • autogpt_platform/backend/backend/data/graph.py
autogpt_platform/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Format Python code with poetry run format

Files:

  • autogpt_platform/backend/backend/data/graph.py
autogpt_platform/**/data/*.py

📄 CodeRabbit inference engine (AGENTS.md)

For changes touching data/*.py, validate user ID checks or explain why not needed

Files:

  • autogpt_platform/backend/backend/data/graph.py
🧠 Learnings (9)
📚 Learning: 2026-02-04T16:49:42.490Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-02-04T16:49:42.490Z
Learning: Applies to autogpt_platform/frontend/**/*.{tsx,ts} : Use generated API hooks from '@/app/api/__generated__/endpoints/' instead of deprecated 'BackendAPI' or 'src/lib/autogpt-server-api/*'

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/build/hooks/useSaveGraph.ts
  • autogpt_platform/frontend/src/app/(platform)/build/stores/edgeStore.ts
📚 Learning: 2026-02-04T16:49:42.490Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-02-04T16:49:42.490Z
Learning: Applies to autogpt_platform/frontend/**/*.{ts,tsx} : Regenerate API hooks with 'pnpm generate:api' after backend OpenAPI spec changes in frontend development

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/build/hooks/useSaveGraph.ts
  • autogpt_platform/frontend/src/app/(platform)/build/stores/edgeStore.ts
📚 Learning: 2026-02-04T16:50:51.495Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-04T16:50:51.495Z
Learning: Applies to autogpt_platform/frontend/src/**/*.{ts,tsx} : Use generated API hooks from `@/app/api/__generated__/endpoints/` with pattern `use{Method}{Version}{OperationName}` and regenerate with `pnpm generate:api`

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/build/hooks/useSaveGraph.ts
📚 Learning: 2026-02-04T16:50:33.615Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: autogpt_platform/frontend/CLAUDE.md:0-0
Timestamp: 2026-02-04T16:50:33.615Z
Learning: Applies to autogpt_platform/frontend/src/**/*.{ts,tsx} : Use generated API hooks from `@/app/api/__generated__/endpoints/` with pattern `use{Method}{Version}{OperationName}`

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/build/hooks/useSaveGraph.ts
📚 Learning: 2026-02-04T16:50:33.615Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: autogpt_platform/frontend/CLAUDE.md:0-0
Timestamp: 2026-02-04T16:50:33.615Z
Learning: Applies to autogpt_platform/frontend/src/**/*.{ts,tsx} : Fully capitalize acronyms in symbols, e.g. `graphID`, `useBackendAPI`

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/build/hooks/useSaveGraph.ts
📚 Learning: 2026-02-04T16:50:51.495Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-04T16:50:51.495Z
Learning: Applies to autogpt_platform/frontend/src/**/*.{ts,tsx} : Avoid large hooks, abstract logic into `helpers.ts` files when sensible

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/build/hooks/useSaveGraph.ts
📚 Learning: 2026-02-04T16:50:51.495Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-04T16:50:51.495Z
Learning: Applies to autogpt_platform/frontend/src/app/(platform)/**/*.tsx : If adding protected frontend routes, update `frontend/lib/supabase/middleware.ts`

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/build/hooks/useSaveGraph.ts
📚 Learning: 2026-02-04T16:50:33.615Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: autogpt_platform/frontend/CLAUDE.md:0-0
Timestamp: 2026-02-04T16:50:33.615Z
Learning: Applies to autogpt_platform/frontend/src/**/*.{ts,tsx} : Separate render logic (`.tsx`) from business logic (`use*.ts` hooks)

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/build/hooks/useSaveGraph.ts
📚 Learning: 2026-02-04T16:49:42.490Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-02-04T16:49:42.490Z
Learning: Applies to autogpt_platform/frontend/**/*.{ts,tsx} : No barrel files or 'index.ts' re-exports in frontend code

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/build/stores/edgeStore.ts
🧬 Code graph analysis (3)
autogpt_platform/frontend/src/app/(platform)/build/hooks/useSaveGraph.ts (2)
autogpt_platform/backend/backend/data/graph.py (1)
  • GraphModel (403-1015)
autogpt_platform/frontend/src/app/(platform)/build/components/helper.ts (1)
  • linkToCustomEdge (70-80)
autogpt_platform/frontend/src/app/(platform)/build/stores/edgeStore.ts (1)
autogpt_platform/frontend/src/app/(platform)/build/components/helper.ts (1)
  • customEdgeToLink (82-89)
autogpt_platform/backend/backend/data/graph.py (1)
autogpt_platform/backend/backend/blocks/__init__.py (1)
  • get_block (130-132)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: types
  • GitHub Check: Seer Code Review
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.13)
  • GitHub Check: end-to-end tests
  • GitHub Check: Check PR Status
🔇 Additional comments (4)
autogpt_platform/frontend/src/app/(platform)/build/stores/edgeStore.ts (1)

123-136: LGTM — defensive filtering before save.

Clean approach: building a Set of node IDs once and filtering in a single pass is efficient. The console.warn is helpful for debugging orphan edge issues without being noisy in production.

autogpt_platform/frontend/src/app/(platform)/build/hooks/useSaveGraph.ts (3)

25-35: Good: module-level helper avoids unnecessary re-renders and closure dependencies.

The function correctly replaces all frontend edges with the backend-authoritative state. The undefined guard on Line 30 is appropriate.

One minor note: if the backend ever returns links: undefined due to a serialization issue, the frontend will silently keep stale edges. Consider logging a warning in the else branch if this is unexpected.


80-82: LGTM — post-create sync.

Correctly syncs edges after the graph is created on the backend.


120-122: LGTM — post-update sync.

Correctly syncs edges after the graph is updated on the backend.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment thread autogpt_platform/backend/backend/data/graph.py
Comment thread autogpt_platform/frontend/src/app/(platform)/build/stores/edgeStore.ts Outdated
@Otto-AGPT

Copy link
Copy Markdown
Contributor Author

Re: Sentry Bug Prediction (comment r2807474296)

Good catch on the history pollution issue with addLinks, but this is pre-existing behavior in the codebase, not introduced by this PR.

My changes in this PR actually improve the situation:

  • syncEdgesWithBackend() in useSaveGraph.ts uses setEdges() for bulk replacement, which doesn't trigger individual history entries
  • The getBackendLinks() filter I added only affects what gets sent to the backend, not history

The addLinks history pollution is a valid issue but should be addressed in a separate PR that refactors the history initialization flow.

@Otto-AGPT

Copy link
Copy Markdown
Contributor Author

Fixed the history pollution issue raised by Sentry. Changed addLinks to bulk-add edges with a single history push instead of calling addEdge individually for each link.

Before: Each addEdge call → separate history entry → N entries for N edges
After: Collect all edges → single set() call → single history entry

Comment thread autogpt_platform/frontend/src/app/(platform)/build/hooks/useSaveGraph.ts Outdated
Comment thread autogpt_platform/frontend/src/app/(platform)/build/stores/edgeStore.ts Outdated
@Otto-AGPT

Copy link
Copy Markdown
Contributor Author

Fixed the race condition identified by Sentry.

Problem: Two separate useEffect hooks were running independently:

  • One for adding nodes (waits for customNodes which depends on blocks API)
  • One for adding links (runs as soon as graph.links is available)

Since links can be available before nodes, addLinks would execute and filter out ALL links because their source/sink nodes didn't exist yet.

Fix: Combined both into a single useEffect that:

  1. First adds nodes to the store
  2. Then adds links (only after nodes are present)

This ensures the correct ordering and prevents silent connection loss.

@Otto-AGPT

Copy link
Copy Markdown
Contributor Author

Re: Sentry Bug Prediction (comment r2807845064) - Edge changes lost during save

This is a valid UX concern but out of scope for this PR. The issue exists because:

  1. Save is async and takes time
  2. User can continue editing during save
  3. syncEdgesWithBackend replaces edges with backend state on success

The suggested fix (locking the entire editor during save) is a significant UX change that should be addressed in a dedicated PR. It requires:

  • Global lock state for the flow editor
  • Disabling node/edge interactions
  • Visual indicator that save is in progress

For now, the save operation is intentionally non-blocking to keep the editor responsive. If edge loss during save becomes a user-reported issue, we can prioritize the locking feature.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
`@autogpt_platform/frontend/src/app/`(platform)/build/components/FlowEditor/Flow/useFlow.ts:
- Around line 138-150: The effect currently clears nodes via
useNodeStore.getState().setNodes([]) but only clears edges inside the
graph?.links guard, so stale edges can persist when graph.links is nullish;
update the useEffect (the block handling customNodes and graph?.links) to always
call useEdgeStore.getState().setEdges([]) alongside
useNodeStore.getState().setNodes([]) before adding nodes/links (i.e., move the
edge reset out of the if (graph?.links) guard), then call addLinks(graph.links)
only if graph?.links exists; keep addNodes(customNodes) as-is.
🧹 Nitpick comments (1)
autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/useFlow.ts (1)

150-150: graph?.links as a dependency triggers re-runs on every query refetch

graph?.links is an array reference — React's useEffect compares dependencies by reference. Each time React Query refetches or returns a new graph object (e.g., window refocus, cache invalidation), graph.links will be a new array even if the content is identical, causing this effect to re-run and re-add all nodes and edges unnecessarily.

Consider using a stable derived value (e.g., linkCount which is already computed on line 230, or a JSON-serialized key) as the dependency instead, or rely on graph object identity alone if React Query's structural sharing keeps it stable for your use case.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 79748ca and 2be589c.

📒 Files selected for processing (1)
  • autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/useFlow.ts
🧰 Additional context used
📓 Path-based instructions (10)
autogpt_platform/frontend/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

autogpt_platform/frontend/**/*.{ts,tsx,js,jsx}: Use Node.js 21+ with pnpm package manager for frontend development
Always run 'pnpm format' for formatting and linting code in frontend development

Files:

  • autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/useFlow.ts
autogpt_platform/frontend/**/*.{tsx,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

autogpt_platform/frontend/**/*.{tsx,ts}: Use function declarations for components and handlers (not arrow functions) in React components
Only use arrow functions for small inline lambdas (map, filter, etc.) in React components
Use PascalCase for component names and camelCase with 'use' prefix for hook names in React
Use Tailwind CSS utilities only for styling in frontend components
Use design system components from 'src/components/' (atoms, molecules, organisms) in frontend development
Never use 'src/components/legacy/' in frontend code
Only use Phosphor Icons (@phosphor-icons/react) for icons in frontend components
Use generated API hooks from '@/app/api/generated/endpoints/' instead of deprecated 'BackendAPI' or 'src/lib/autogpt-server-api/
'
Use React Query for server state (via generated hooks) in frontend development
Default to client components ('use client') in Next.js; only use server components for SEO or extreme TTFB needs
Use '' component for rendering errors in frontend UI; use toast notifications for mutation errors; use 'Sentry.captureException()' for manual exceptions
Separate render logic from data/behavior in React components; keep comments minimal (code should be self-documenting)

Files:

  • autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/useFlow.ts
autogpt_platform/frontend/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

autogpt_platform/frontend/**/*.{ts,tsx}: No barrel files or 'index.ts' re-exports in frontend code
Regenerate API hooks with 'pnpm generate:api' after backend OpenAPI spec changes in frontend development

Files:

  • autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/useFlow.ts
autogpt_platform/frontend/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (autogpt_platform/frontend/CLAUDE.md)

autogpt_platform/frontend/src/**/*.{ts,tsx}: Fully capitalize acronyms in symbols, e.g. graphID, useBackendAPI
Use function declarations (not arrow functions) for components and handlers
Separate render logic (.tsx) from business logic (use*.ts hooks)
Use shadcn/ui (Radix UI primitives) with Tailwind CSS styling for UI components
Use Phosphor Icons only for icons
Use ErrorCard for render errors, toast for mutations, and Sentry for exceptions
Use design system components from src/components/ (atoms, molecules, organisms)
Never use src/components/__legacy__/* components
Use generated API hooks from @/app/api/__generated__/endpoints/ with pattern use{Method}{Version}{OperationName}
Use Tailwind CSS only for styling, with design tokens
Do not use useCallback or useMemo unless asked to optimize a given function
Never type with any unless a variable/attribute can ACTUALLY be of any type

autogpt_platform/frontend/src/**/*.{ts,tsx}: Structure components as ComponentName/ComponentName.tsx + useComponentName.ts + helpers.ts and use design system components from src/components/ (atoms, molecules, organisms)
Use generated API hooks from @/app/api/__generated__/endpoints/ with pattern use{Method}{Version}{OperationName} and regenerate with pnpm generate:api
Use function declarations (not arrow functions) for components and handlers
Separate render logic from business logic with component.tsx + useComponent.ts + helpers.ts structure
Colocate state when possible, avoid creating large components, use sub-components in local /components folder
Avoid large hooks, abstract logic into helpers.ts files when sensible
Use arrow functions only for callbacks, not for component declarations
Avoid comments at all times unless the code is very complex
Do not use useCallback or useMemo unless asked to optimize a given function

Files:

  • autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/useFlow.ts
autogpt_platform/frontend/src/app/(platform)/**/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (autogpt_platform/frontend/CLAUDE.md)

Put sub-components in local components/ folder within feature directories

Files:

  • autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/useFlow.ts
autogpt_platform/frontend/src/**/*use*.ts

📄 CodeRabbit inference engine (autogpt_platform/frontend/CLAUDE.md)

Do not type hook returns, let TypeScript infer as much as possible

Files:

  • autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/useFlow.ts
autogpt_platform/frontend/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

autogpt_platform/frontend/**/*.{js,jsx,ts,tsx}: Format frontend code using pnpm format
Never use components from src/components/__legacy__/*

Files:

  • autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/useFlow.ts
autogpt_platform/frontend/**/*.{js,jsx,ts,tsx,css}

📄 CodeRabbit inference engine (AGENTS.md)

Use Tailwind CSS only for styling, use design tokens, and use Phosphor Icons only

Files:

  • autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/useFlow.ts
autogpt_platform/frontend/src/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Do not type hook returns, let Typescript infer as much as possible

Files:

  • autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/useFlow.ts
autogpt_platform/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Never type with any, if no types available use unknown

Files:

  • autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/useFlow.ts
🧠 Learnings (3)
📚 Learning: 2026-02-04T16:50:33.615Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: autogpt_platform/frontend/CLAUDE.md:0-0
Timestamp: 2026-02-04T16:50:33.615Z
Learning: Use xyflow/react for visual graph editor in Workflow Builder

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/useFlow.ts
📚 Learning: 2026-02-04T16:50:33.615Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: autogpt_platform/frontend/CLAUDE.md:0-0
Timestamp: 2026-02-04T16:50:33.615Z
Learning: Applies to autogpt_platform/frontend/src/**/*.{ts,tsx} : Separate render logic (`.tsx`) from business logic (`use*.ts` hooks)

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/useFlow.ts
📚 Learning: 2026-02-04T16:50:51.495Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-04T16:50:51.495Z
Learning: Applies to autogpt_platform/frontend/src/**/*.{ts,tsx} : Structure components as `ComponentName/ComponentName.tsx` + `useComponentName.ts` + `helpers.ts` and use design system components from `src/components/` (atoms, molecules, organisms)

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/useFlow.ts
🧬 Code graph analysis (1)
autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/useFlow.ts (2)
autogpt_platform/frontend/src/app/(platform)/build/stores/nodeStore.ts (1)
  • useNodeStore (123-691)
autogpt_platform/frontend/src/app/(platform)/build/stores/edgeStore.ts (1)
  • useEdgeStore (40-257)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: types
  • GitHub Check: Seer Code Review
  • GitHub Check: end-to-end tests
  • GitHub Check: Analyze (python)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.13)
  • GitHub Check: Check PR Status
🔇 Additional comments (1)
autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/useFlow.ts (1)

136-148: Good fix: combining node and link initialization eliminates the race condition.

Sequencing the node addition before link addition within a single effect is the right approach to ensure nodes exist in the store when addLinks validates link endpoints. The bulk setEdges([]) + addLinks() pattern also avoids per-edge history pollution.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we can remove the changes from this file — this will create a race condition with useEffect in useFlow.ts, and there’s no need for it tbh.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done! Removed syncEdgesWithBackend and all its call sites. The backend-returned data still flows through the query cache and triggers the useFlow.ts useEffect naturally, so this was indeed redundant.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we can create a new file called linkValidations.ts and add these two validations there: filterValidEdges() and filterValidLinks().
but in the best-case scenario, our backend should not send a broken agent to the frontend.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done! Created linkValidations.ts with filterValidEdges() and filterValidLinks(), and updated edgeStore.ts to use them.

Agreed on the backend point — the backend's prune_invalid_links() in graph.py should catch these before they reach the frontend. The frontend validation is defense-in-depth for edge cases (e.g., concurrent edits, stale cache).

@github-project-automation github-project-automation Bot moved this from 🆕 Needs initial review to 🚧 Needs work in AutoGPT development kanban Feb 17, 2026
Fixes graph desync issues where edge deletions don't persist, causing
stale connections that users cannot remove.

Frontend changes:
- edgeStore: Validate links during addLinks() - skip edges referencing
  non-existent nodes
- edgeStore: Filter invalid edges in getBackendLinks() before save
- useSaveGraph: Sync edge store with authoritative backend state after
  save to prevent desync

Backend changes:
- graph.py: Add prune_invalid_links() method that removes:
  - Links referencing non-existent nodes
  - Links with invalid block IDs
- Called during graph validation to auto-cleanup orphan edges

This ensures:
1. Invalid edges are filtered out when loading a graph
2. Invalid edges are not sent to backend during save
3. Frontend syncs with backend state after save
4. Backend cleans up any orphan edges that slip through

Closes: SECRT-1959
Changed addLinks to collect all valid edges first and add them in a single
set() call with one history push, instead of calling addEdge for each link
which would push to history for every edge individually.

Fixes: Sentry bug prediction about undo/redo history pollution
Combined the separate useEffect hooks for adding nodes and links into
a single effect that ensures nodes are added first. Previously, links
could be processed before nodes existed in the store, causing all
connections to be silently filtered out.

Fixes: Sentry bug prediction about race condition on graph load
Moved setEdges([]) outside the graph?.links guard to prevent stale edges
from persisting when loading a graph with no links.
…syncEdgesWithBackend

- Remove syncEdgesWithBackend from useSaveGraph.ts to avoid race condition
  with useFlow.ts useEffect (per Abhi's review)
- Extract filterValidEdges() and filterValidLinks() into linkValidations.ts
  for cleaner separation of concerns (per Abhi's review)
- Update edgeStore.ts to use extracted validation functions
@Otto-AGPT
Otto-AGPT force-pushed the otto/secrt-1959-fix-graph-edge-desync branch from e19b6e4 to dee131b Compare February 26, 2026 15:04

@Otto-AGPT Otto-AGPT left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Review from Otto 🐙

Overall: Solid defensive fix for a real user-facing bug. The multi-layer approach (backend pruning + frontend validation + load-time ordering) is appropriate for this kind of desync issue.

What's good

  • Backend prune_invalid_links() — clean implementation, good logging, correctly placed before structure validation
  • useFlow.ts race condition fix — combining node+edge loading into one useEffect is the right call. The original two separate effects had different dependency timing
  • linkValidations.ts extraction — clean separation per Abhi's feedback
  • History pollution fix — bulk-adding edges with a single history push instead of individual addEdge calls

Minor observations

  1. prune_invalid_links calls get_block() twice per link (once for source, once for sink). For graphs with many links sharing the same blocks, a block cache could avoid redundant lookups. Not a blocker — the get_block() function likely already caches via the block registry.

  2. useFlow.ts dependency array includes graph?.links — this is a reference comparison on an array. If the parent query refetches and returns a new array object with the same data, this effect will re-run (clearing and re-adding all nodes/edges). Works correctly but worth noting if performance becomes a concern on large graphs.

  3. The useEdgeStore.getState().setEdges([]) is now always called alongside node reset even when graph?.links is nullish — good, this was the CodeRabbit suggestion and prevents stale edges.

No blocking issues. Looks good to me. 👍

@majdyz majdyz closed this Mar 3, 2026
@github-project-automation github-project-automation Bot moved this to Done in Frontend Mar 3, 2026
@github-project-automation github-project-automation Bot moved this from 🚧 Needs work to ✅ Done in AutoGPT development kanban Mar 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

platform/backend AutoGPT Platform - Back end platform/frontend AutoGPT Platform - Front end size/l

Projects

Status: ✅ Done
Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants