Skip to content

fix: pipeline item lookup wrong key + stringified label arrays - #74

Open
mateusbellozupko wants to merge 1 commit into
evolution-foundation:mainfrom
mateusbellozupko:fix/pipeline-item-lookup-and-stringified-labels
Open

mateusbellozupko wants to merge 1 commit into
evolution-foundation:mainfrom
mateusbellozupko:fix/pipeline-item-lookup-and-stringified-labels

Conversation

@mateusbellozupko

@mateusbellozupko mateusbellozupko commented Sep 22, 2026 •

Copy link
Copy Markdown

Summary

Two independent bugs found while investigating a live incident where a qualified lead's custom_fields and labels were never actually saved to its pipeline card:

  1. pipeline_manipulation.py — the create_task pipeline-item lookup read items_response.get("payload", []), but GET /pipelines/{id}/pipeline_items wraps its list under "data" ({"success": true, "data": [...], "meta": {...}}) — "payload" belongs to a different endpoint shape (conversation labels). This silently returned an empty list every time, so the lookup always missed even when the pipeline item existed.
  2. manage_conversation_labels.py — _coerce_input_list received labels='["a", "b", "c"]' as a single string (GLM via OpenRouter stringified the array instead of emitting real JSON), and treated the whole bracketed string as one literal label — which never matched the catalog and was silently rejected. The model then surfaced this to the user as "these labels don't exist", a hallucinated explanation for what was actually a malformed tool argument. _coerce_input_list now detects this shape and parses it back into a real list, falling back to the previous literal-string behavior if the bracketed string isn't valid JSON.

Test plan

  • tests/unit/test_coerce_input_list.py — new, covers the live regression plus list/None/malformed-input paths, 6/6 passing
  • Full pytest tests/unit/ (excluding a pre-existing, unrelated test_exception_handlers.py collection error) — 394/394 passing
  • No existing test exercised pipeline_manipulation's inline GET-list-and-match logic (only the caller was mocked), so bug 1 has no isolated unit test in this repo — confirmed live and covered with a dedicated test in the private fork's extracted _find_pipeline_item_id helper

🤖 Generated with Claude Code

Summary by Sourcery

Fix pipeline card updates and conversation label handling for malformed or mismatched tool inputs.

Bug Fixes:

  • Correct pipeline item lookups to read the API response data and successfully associate existing items with conversations.
  • Parse model-supplied stringified label arrays while preserving fallback handling for malformed inputs.

Tests:

  • Add unit coverage for stringified arrays, normal inputs, malformed values, deduplication, and null handling.

Two independent bugs found investigating a live incident where a
qualified lead's custom_fields and labels were never actually saved:

1. pipeline_manipulation.py's create_task pipeline-item lookup read
   `items_response.get("payload", [])`, but GET
   /pipelines/{id}/pipeline_items wraps its list under "data"
   (`{"success": true, "data": [...], "meta": {...}}`) — "payload"
   belongs to a different endpoint shape (conversation labels). This
   silently returned an empty list every time, so the lookup always
   missed even when the pipeline item existed.

2. manage_conversation_labels.py's _coerce_input_list received
   labels='["a", "b", "c"]' as a single string (GLM via OpenRouter
   stringified the array instead of emitting real JSON), and treated
   the whole bracketed string as one literal label — which never
   matched the catalog and was silently rejected. The model then
   surfaced this to the user as "these labels don't exist", a
   hallucinated explanation for a malformed tool argument.
   _coerce_input_list now detects this shape and parses it back into
   a real list, falling back to the previous literal-string behavior
   if the bracketed string isn't valid JSON.

Added tests/unit/test_coerce_input_list.py covering both the live
regression and the list/None/malformed-input paths. No existing test
exercised pipeline_manipulation's inline GET-list-and-match logic
(only the caller was mocked), so bug 1 has no isolated unit test here
— it was confirmed live and via the private fork's extracted
_find_pipeline_item_id, covered there in a dedicated test file.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sorry @mateusbellozupko, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 1 day and 10 hours by commenting @sourcery-ai review. Upgrade to get a review now.

@sourcery-ai

sourcery-ai Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

Fixes two independent data-handling bugs: pipeline cards now resolve existing items from the API's data field, and stringified JSON label arrays from the model are parsed into individual labels while malformed inputs retain safe literal-string behavior.

Sequence diagram for pipeline item lookup

sequenceDiagram
    participant CreateTask
    participant PipelineAPI
    CreateTask->>PipelineAPI: GET /pipelines/{id}/pipeline_items
    PipelineAPI-->>CreateTask: data: pipeline items
    CreateTask->>CreateTask: item.get(conversation_id)
    CreateTask->>CreateTask: item.get(id)
Loading

Flow diagram for stringified label array coercion

flowchart TD
    A[labels input] --> B{value is None}
    B -->|yes| C[Return empty list]
    B -->|no| D{Bracketed string}
    D -->|yes| E[json.loads]
    E --> F{Parsed value is list}
    F -->|yes| G[Process list items]
    F -->|no or invalid JSON| H[Process as literal string]
    D -->|no| I{String or list}
    I --> G
    I --> H
    G --> J[Deduplicate string labels]
    H --> J
Loading

File-Level Changes

Change Details Files
Correct the pipeline-item response field used during task creation so existing conversation cards are found and updated instead of treated as missing.
  • Read pipeline items from the API response's data array rather than the unrelated payload field.
  • Match the returned item by conversation_id and retain its ID for subsequent card updates.
src/services/adk/tools/evo_crm/pipeline_manipulation.py
Normalize model-generated stringified label arrays before label validation and deduplication.
  • Detect bracketed string inputs and parse valid JSON arrays.
  • Preserve the prior single-string behavior for malformed or non-array values.
  • Add regression coverage for lists, scalar strings, stringified arrays, malformed input, deduplication, and None.
src/services/adk/tools/evo_crm/manage_conversation_labels.py
tests/unit/test_coerce_input_list.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@mateusbellozupko

Copy link
Copy Markdown
Author

@sourcery-ai review

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've reviewed your changes and they look great!

Sourcery assessment

Needs a human reviewer. The changes can persist conversation-label updates and associate newly created tasks with a pipeline item; if the parsing or lookup is wrong, those records may need manual correction or cleanup. Reverting stops future incorrect writes but does not undo values or associations already stored.


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

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.

1 participant