Skip to content

fix: session_id collisions, concurrent handoff corruption, async retry hook crash - #3398

Merged
MervinPraison merged 2 commits into
mainfrom
claude/issue-3397-20260726-0715
Jul 29, 2026
Merged

fix: session_id collisions, concurrent handoff corruption, async retry hook crash#3398
MervinPraison merged 2 commits into
mainfrom
claude/issue-3397-20260726-0715

Conversation

@praisonai-triage-agent

@praisonai-triage-agent praisonai-triage-agent Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Fixes #3397

Summary

Three minimal, focused fixes in the praisonaiagents core SDK, each independently verified against the current source:

Gap 1 — session_id collision (agent/memory_mixin.py)

Default session_id was sha256(agent.name)[:6] + UTC hour, so two same-named agents (e.g. two unnamed Agent()s → name "Agent") in the same hour computed an identical id and bled chat history via on_agent_start. Appended a per-instance uuid4().hex[:8] suffix so collisions are impossible.

Gap 2 — concurrent handoff corruption (agent/handoff.py)

threading.local() isolates per OS thread, not per asyncio.Task, so parallel_handoffs (which uses asyncio.gather on one event-loop thread) shared a single handoff chain — corrupting cycle/depth detection. Replaced with contextvars.ContextVar, which isolates per Task across await points (mirrors the existing pattern in tools/injected.py).

Gap 3 — async retry hook crash (hooks/runner.py)

chat_mixin.py's async retry path calls self._hook_runner.execute_async(...), but HookRunner had no such method → AttributeError on every retryable async error, breaking retry/backoff. Added execute_async as an async alias of execute().

Testing

  • Verified per-asyncio.Task isolation of the handoff chain (depths all 1 under gather; would be 1..5 with the old thread-local).
  • Confirmed HookRunner.execute_async exists and is a coroutine.
  • Hook tests: 46 passed. The 5 pre-existing test_handoff_tool_policy.py failures are unrelated (reproduce on the base commit before these changes).

Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added asynchronous hook execution support.
    • Improved reliability for concurrent agent handoffs by isolating execution context across async tasks.
    • Added more unique automatically generated session identifiers to prevent collisions.
  • Bug Fixes

    • Resolved potential session ID conflicts between agent instances running within the same hour.

@MervinPraison

Copy link
Copy Markdown
Owner

@coderabbitai review

@MervinPraison

Copy link
Copy Markdown
Owner

/review

@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@MervinPraison MervinPraison added pipeline/blocked:ci Blocked: CI not green on HEAD pipeline/blocked:manual-review Blocked: requires manual review pipeline/blocked:no-final Blocked: no FINAL @claude trigger yet pipeline/final-claude-pending Reviews done; waiting for FINAL @claude labels Jul 26, 2026
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7b6d9e65-507d-4528-b243-55c999553fa8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR isolates handoff chains per async context, adds random suffixes to generated memory session IDs, and introduces an asynchronous HookRunner execution method.

Changes

Agent execution updates

Layer / File(s) Summary
Context-local handoff tracking
src/praisonai-agents/praisonaiagents/agent/handoff.py
Replaces thread-local handoff-chain storage with contextvars.ContextVar access and reset logic.
Unique default session identifiers
src/praisonai-agents/praisonaiagents/agent/memory_mixin.py
Adds an 8-character UUID suffix to generated session IDs in async and sync initialization branches.
Async hook runner entry point
src/praisonai-agents/praisonaiagents/hooks/runner.py
Adds HookRunner.execute_async() to await the existing hook execution method with the same arguments and filters.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested labels: pipeline/reviews-pending

Suggested reviewers: mervinpraison

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately names the three fixes: session_id collisions, concurrent handoff corruption, and async retry hook failure.
Linked Issues check ✅ Passed The changes address all three linked requirements: collision-resistant session IDs, ContextVar handoff isolation, and an async HookRunner path.
Out of Scope Changes check ✅ Passed The diff stays within the linked issue scope and does not introduce unrelated feature work.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/issue-3397-20260726-0715

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.

@greptile-apps

greptile-apps Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR applies three focused reliability fixes in the core SDK.

  • Generates unique default session IDs for individual agent instances.
  • Isolates inherited handoff chains across tasks created by parallel handoffs.
  • Adds the missing asynchronous hook-runner entry point used during retries.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains, and the previously reported inherited handoff-chain sharing issue is addressed by rebinding a copied chain within each task scheduled by asyncio.gather.

Important Files Changed

Filename Overview
src/praisonai-agents/praisonaiagents/agent/handoff.py Copies the inherited handoff chain inside each coroutine scheduled by asyncio.gather, resolving the previously reported mutable-context sharing issue.
src/praisonai-agents/praisonaiagents/agent/memory_mixin.py Adds a random per-instance suffix to automatically generated session identifiers.
src/praisonai-agents/praisonaiagents/hooks/runner.py Adds an asynchronous alias that delegates to the existing hook execution implementation.

Reviews (3): Last reviewed commit: "fix(handoff): isolate handoff chain per ..." | Re-trigger Greptile

Comment thread src/praisonai-agents/praisonaiagents/agent/handoff.py Outdated
@MervinPraison

Copy link
Copy Markdown
Owner

@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAI (not a fork), you are able to make modifications to this branch and push directly. SCOPE: Focus ONLY on Python packages (praisonaiagents, praisonai). Do NOT modify praisonai-rust or praisonai-ts. Read ALL comments above from Gemini, Qodo, CodeRabbit, and Copilot carefully before responding.

Phase 1: Review per AGENTS.md

  1. Protocol-driven: check heavy implementations vs core SDK
  2. Backward compatible: ensure zero feature regressions
  3. Performance: no hot-path regressions
  4. SDK value: review in depth whether the change genuinely adds value to the SDK — never add features for the sake of adding them. It must strengthen the SDK (simpler, more user-friendly, robust, world-class, secure). If it does not clearly add value, request changes or recommend rejecting/closing rather than merging scope creep
  5. Do not bloat the Agent class with additional params — only if absolutely required; we already support many params.
  6. Repo routing: agent-callable tools → PraisonAI-Tools; lifecycle plugins → PraisonAI-Plugins; optional sandbox backends → PraisonAI-Plugins (praisonai.sandbox entry point) — request changes if wrongly added to praisonaiagents/

Phase 2: FIX Valid Issues
7. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix
8. Also independently identify and fix any gaps or issues you find in the changed code — do not rely only on prior reviewer feedback
9. Push all code fixes directly to THIS branch (do NOT create a new PR)
10. Comment a summary of exact files modified and what you skipped

Phase 3: Final Verdict
11. If all issues are resolved, approve the PR / close the Issue
12. If blocking issues remain, request changes / leave clear action items

@MervinPraison MervinPraison added pipeline/awaiting-merge-gate FINAL done; waiting for merge gate / CI pipeline/blocked:cooldown Blocked: post-push or @claude cooldown and removed pipeline/final-claude-pending Reviews done; waiting for FINAL @claude pipeline/blocked:no-final Blocked: no FINAL @claude trigger yet labels Jul 26, 2026
@praisonai-triage-agent

praisonai-triage-agent Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

Claude finished @MervinPraison's task —— View job


Final Architecture Review — PR #3398

Verdict: APPROVE (fixed the one valid blocking issue; pushed to this branch)

  • Read guidelines & all reviewer comments
  • Reviewed all 3 fixes independently against source
  • Fixed Greptile P1 (shared handoff chain)
  • Tested (P1 isolation + hook/handoff suites)
  • Pushed to claude/issue-3397-20260726-0715

Phase 1 — Review of the three fixes

Gap 1 — session_id collision (memory_mixin.py) ✅ Valid & minimal
sha256(name)[:6] + hour collided for same-named agents. Appending uuid4().hex[:8] makes collisions impossible. Both sync/async lock paths updated symmetrically under double-checked locking. No new params, no Agent bloat. Lightweight and correct.

Gap 2 — concurrent handoff corruption (handoff.py) ✅ Valid direction, but incomplete — see Phase 2
threading.local()contextvars.ContextVar is the right move and mirrors the existing tools/injected.py pattern. However the ContextVar stored a mutable list, and Greptile's P1 was correct: asyncio.gather copies the context per task but the copied binding still points at the same parent list object. The author's test only exercised an empty top-level chain (where .get() returns None and each child lazily creates its own list), so it passed — but with a non-empty parent chain, siblings mutate a shared list and corrupt cycle/depth tracking.

Gap 3 — async retry hook crash (hooks/runner.py) ✅ Valid & minimal
Confirmed live consumers: chat_mixin.py:4646 and execution_mixin.py:1736 call execute_async, which did not exist → AttributeError on every retryable async error. The added execute_async is a clean async alias of execute(). Correct.

Phase 2 — Fix implemented (pushed: 08b794b76)

agent/handoff.pyparallel_handoffs._run_one: at each task's entry, rebind the ContextVar to a fresh per-task copy of the parent chain:

_handoff_chain_var.set(list(_handoff_chain_var.get() or []))

Since each _run_one already runs in its own copied context, a .set() to a new list object fully isolates sibling mutations regardless of whether the parent chain was empty or not. Minimal (7 lines, no API/param changes, no Agent bloat).

Verification (reproducing the P1 scenario with a non-empty [Parent] chain under gather):

per-child depths (expect all 2):        [2, 2, 2]      ✓
per-child residual chains (expect [Parent]): [['Parent'],['Parent'],['Parent']]  ✓
parent chain after gather (expect [Parent]): ['Parent']  ✓

Before this fix, depths would drift and the shared list would be corrupted by sibling pops.

Testing

  • Hook + handoff suites: 120 passed, 5 failed.
  • The 5 failures are pre-existing in test_handoff_tool_policy.py ('Mock' object is not iterable, cannot unpack non-iterable Mock — test-setup issues). They reproduce on the base commit, contain zero references to parallel_handoffs/handoff-chain internals, and are unrelated to this PR.

Skipped / not changed

  • praisonai-rust, praisonai-ts — out of scope.
  • No Agent params added; no scope creep. All three fixes are minimal, backward-compatible, and strengthen SDK robustness.

All reviewer-flagged and independently-found issues are resolved. Approving.

--- • claude/issue-3397-20260726-0715

@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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/praisonai-agents/praisonaiagents/agent/handoff.py`:
- Around line 167-181: Update _handoff_chain_var and _get_handoff_chain to store
immutable tuple values, using None or an empty tuple for cleared state. Replace
in-place push, pop, and clear mutations throughout the handoff flow with
assignments of newly built tuples, and return a fresh list when constructing
HandoffInputData so tasks cannot share mutable chain state.

In `@src/praisonai-agents/praisonaiagents/agent/memory_mixin.py`:
- Around line 243-261: Update session ID generation in both lock branches to use
the full UUID suffix instead of truncating uuid.uuid4().hex to eight characters.
Preserve the existing hour and agent_hash components, and verify storage or
routing consumers accept the resulting longer session IDs without shortening
them.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 84b0c8ee-8489-4450-8c0d-943f606f7989

📥 Commits

Reviewing files that changed from the base of the PR and between 631d2e9 and cd53acc.

📒 Files selected for processing (3)
  • src/praisonai-agents/praisonaiagents/agent/handoff.py
  • src/praisonai-agents/praisonaiagents/agent/memory_mixin.py
  • src/praisonai-agents/praisonaiagents/hooks/runner.py

Comment on lines +167 to +181
# Context-local storage for tracking handoff chains.
# Uses contextvars (not threading.local) so state is correctly isolated per
# asyncio.Task across await points — required for parallel_handoffs, which runs
# multiple handoffs concurrently on the same event-loop thread via asyncio.gather.
_handoff_chain_var: contextvars.ContextVar[Optional[List[str]]] = contextvars.ContextVar(
'handoff_chain', default=None
)

def _get_handoff_chain() -> List[str]:
"""Get current handoff chain from thread-local storage."""
if not hasattr(_handoff_context, 'chain'):
_handoff_context.chain = []
return _handoff_context.chain
"""Get current handoff chain from context-local storage."""
chain = _handoff_chain_var.get()
if chain is None:
chain = []
_handoff_chain_var.set(chain)
return chain

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.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

python - <<'PY'
import asyncio
import contextvars

chain = contextvars.ContextVar("chain", default=None)

def push(value):
    current = chain.get()
    if current is None:
        current = []
        chain.set(current)
    current.append(value)

async def child(value):
    push(value)
    await asyncio.sleep(0)
    return list(chain.get())

async def main():
    chain.set(["parent"])
    first, second = await asyncio.gather(child("first"), child("second"))
    assert first == ["parent", "first"], first
    assert second == ["parent", "second"], second

asyncio.run(main())
PY
# The current mutable-list pattern should fail these assertions.

Repository: MervinPraison/PraisonAI

Length of output: 737


🏁 Script executed:

#!/bin/bash
set -eu

# Locate and inspect the relevant handoff implementation without executing repository code.
if [ -f src/praisonai-agents/praisonaiagents/agent/handoff.py ]; then
  wc -l src/praisonai-agents/praisonaiagents/agent/handoff.py
  sed -n '1,280p' src/praisonai-agents/praisonaiagents/agent/handoff.py | cat -n
fi

Repository: MervinPraison/PraisonAI

Length of output: 12506


🏁 Script executed:

#!/bin/bash
set -eu

# Find all call sites of the handoff chain helpers and identify concurrent/parallel paths.
rg -n "_get_handoff_chain|_push_handoff|_pop_handoff|_clear_handoff_chain|parallel_handoffs|asyncio\.gather|allow_parallel" src/praisonai-agents/praisonaiagents/agent/handoff.py src/praisonai-agents/praisonaiagents/agent -g '*.py'

Repository: MervinPraison/PraisonAI

Length of output: 6091


🏁 Script executed:

#!/bin/bash
set -eu

# Inspect the parallel execution path and any clear/pop behavior around those calls.
sed -n '1100,1230p' src/praisonai-agents/praisonaiagents/agent/handoff.py | cat -n
sed -n '1320,1590p' src/praisonai-agents/praisonaiagents/agent/handoff.py | cat -n

Repository: MervinPraison/PraisonAI

Length of output: 19693


Store an immutable handoff chain in the ContextVar.

ContextVar copies the mapping into each asyncio.Task, but mutable values are shared by identity. If one handoff task mutates the shared list, sibling tasks can see corrupted depth/cycle data or crash when popping. Keep the stored value immutable (None/empty tuple) and build a new tuple for every push/pop/clear; return a fresh list when the chain is passed into HandoffInputData.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/praisonai-agents/praisonaiagents/agent/handoff.py` around lines 167 -
181, Update _handoff_chain_var and _get_handoff_chain to store immutable tuple
values, using None or an empty tuple for cleared state. Replace in-place push,
pop, and clear mutations throughout the handoff flow with assignments of newly
built tuples, and return a fresh list when constructing HandoffInputData so
tasks cannot share mutable chain state.

Source: Coding guidelines

Comment on lines +243 to +261
import uuid
from datetime import datetime, timezone
with self._history_lock._lock._thread_lock:
if self._session_id is None: # Double-check after acquiring lock
hour_str = datetime.now(timezone.utc).strftime("%Y%m%d%H")
agent_hash = hashlib.sha256((self.name or "agent").encode()).hexdigest()[:6]
self._session_id = f"{hour_str}-{agent_hash}"
# per-instance suffix so same-named agents can never collide
self._session_id = f"{hour_str}-{agent_hash}-{uuid.uuid4().hex[:8]}"
else:
# Use sync lock directly
import hashlib
import uuid
from datetime import datetime, timezone
with self._history_lock.lock():
if self._session_id is None: # Double-check after acquiring lock
hour_str = datetime.now(timezone.utc).strftime("%Y%m%d%H")
agent_hash = hashlib.sha256((self.name or "agent").encode()).hexdigest()[:6]
self._session_id = f"{hour_str}-{agent_hash}"
# per-instance suffix so same-named agents can never collide
self._session_id = f"{hour_str}-{agent_hash}-{uuid.uuid4().hex[:8]}"

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.

🔒 Security & Privacy | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

rg -n --glob '*.py' \
  'session_id|uuid\.uuid4|hour_str|agent_hash|YYYYMMDDHH|\[-8:|split\(["'\'']-["'\'']\)' \
  .

Repository: MervinPraison/PraisonAI

Length of output: 50380


🏁 Script executed:

#!/bin/bash
set -eu

echo "== relevant files =="
git ls-files | rg '(^|/)memory_mixin\.py$|(^|/)factories\.py$|(^|/)store\.py$' | sed 's#^\./##'

echo
echo "== memory_mixin context =="
sed -n '220,275p' src/praisonai-agents/praisonaiagents/agent/memory_mixin.py

echo
echo "== factories context =="
sed -n '600,670p' src/praisonaiag

Repository: MervinPraison/PraisonAI

Length of output: 3443


🏁 Script executed:

#!/bin/bash
set -eu

echo "== relevant files =="
git ls-files | rg '(^|/)memory_mixin\.py$|(^|/)factories\.py$|(^|/)store\.py$' | sed 's#^\./##'

echo
echo "== memory_mixin context =="
sed -n '220,275p' src/praisonai-agents/praisonaiagents/agent/memory_mixin.py

echo
echo "== factories context =="
sed -n '600,670p' src/praisonai-agents/praisonaiagents/memory/adapters/factories.py

echo
echo "== session store path handling =="
sed -n '610,635p' src/praisonai-agents/praisonaiagents/session/store.py

echo
echo "== narrow session_id generator occurrences =="
rg -n --glob '*.py' '(_session_id|history_session_id|MemoryConfig|session_id=) = .*uuid\.uuid4\(\)\.hex\[:8\]|uuid\.uuid4\(\)\.hex\[:8\].*session_id|session_id.*uuid\.uuid4\(\)\.hex\[:8\]' src/praisonai-agents

Repository: MervinPraison/PraisonAI

Length of output: 8467


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from math import log2
slots = 16**8
avg_after = (slots // 2) * (1 - ((1 - 1/slots) ** slots))
birthday_50 = int((2 * slots * (2**0.5 * log2(2)) )**0.5) # birthday approx for 50%
print("slots", slots)
print("avg occupied after one million ids", avg_after)
print("half-birthday threshold", birthday_50)
PY

echo
echo "== memory adapter routing/session_id forwarding =="
rg -n --glob '*.py' 'on_agent_start|session_id|session-id|routing|route' src/praisonai-agents/praisonaiagents/memory src/praisonaiagents/praisonaiagents/memory 2>/dev/null || true

echo
echo "== targeted session_id pass-throughs =="
rg -n --glob '*.py' 'session_id=self\._session_id|kwargs\.get\("session_id"|session-id=|route.*session|session.*route' src/praisonai-agents/praisonaiagents src/praisonai 2>/dev/null || true

Repository: MervinPraison/PraisonAI

Length of output: 7832


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from math import log2
slots = 16**8
avg_after = slots * (1 - (1 - 1/slots) ** 1_000_000)
birthday_50 = int((2 * slots * (2**0.5 * log2(2)))**0.5)
print("slots", slots)
print("avg occupied after one million ids", avg_after)
print("half-birthday threshold", birthday_50)
PY

echo
echo "== targeted session_id pass-throughs =="
rg -n --glob '*.py' 'self\._db\.on_agent_start|on_agent_start|session_id=self\._session_id|kwargs\.get\("session_id"|session-id=|route.*session|session.*route' src/praisonai-agents/src/praisonai-agents/praisonaiagents/praisonai 2>/dev/null || true

Repository: MervinPraison/PraisonAI

Length of output: 302


🏁 Script executed:

#!/bin/bash
set -eu

echo "== session store routing methods =="
sed -n '180,215p' src/praisonai-agents/praisonaiagents/session/sqlite_store.py
sed -n '390,422p' src/praisonai-agents/praisonaiagents/session/sqlite_store.py
sed -n '425,443p' src/praisonai-agents/praisonaiagents/session/sqlite_store.py

echo
echo "== memory mixin calls =="
sed -n '330,375p' src/praisonai-agents/praisonaiagents/agent/memory_mixin.py

echo
echo "== focused session routing/session_id usage =="
rg -n --glob '*.py' 'session_id=|gateway_session_id|get_by_gateway_session|route.*session|save_session|_save_session|save_session' \
  src/praisonai-agents/praisonaiagents/agent src/praisonai-agents/praisonaiagents/session src/praisonai-agents/praisonaiagents/memory src/praisonai/praisonai/persistence/hooks/agent_hooks.py \
  | sed -n '1,220p'

Repository: MervinPraison/PraisonAI

Length of output: 20768


Use the full UUID suffix for session IDs.

uuid.uuid4().hex[:8] only provides 32 bits of entropy; with millions of agents or runs sharing the same yearmonthhouroffilename prefix, collisions become likely in bounded-session scenarios. These IDs are used for persistence/storage routing, so a collision can merge or expose another agent’s history. Use the full UUID in both generation paths and verify no storage/routing consumer requires shortening.

[low_effort_and_medium_reward]

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/praisonai-agents/praisonaiagents/agent/memory_mixin.py` around lines 243
- 261, Update session ID generation in both lock branches to use the full UUID
suffix instead of truncating uuid.uuid4().hex to eight characters. Preserve the
existing hour and agent_hash components, and verify storage or routing consumers
accept the resulting longer session IDs without shortening them.

@MervinPraison MervinPraison added pipeline/blocked:stale-final Blocked: FINAL stale after new commits and removed pipeline/blocked:stale-final Blocked: FINAL stale after new commits labels Jul 26, 2026
@praisonai-triage-agent praisonai-triage-agent Bot removed pipeline/blocked:ci Blocked: CI not green on HEAD pipeline/blocked:cooldown Blocked: post-push or @claude cooldown labels Jul 26, 2026
@MervinPraison

Copy link
Copy Markdown
Owner

@claude this PR has merge conflicts with main. Please rebase onto latest main, resolve conflicts (keep this PR's intent, merge in newer main logic), run targeted tests, and force-push with --force-with-lease. Comment which files you resolved. Do not bloat the Agent class with additional params — only if absolutely required; we already support many params. Keep the resolution minimal — the aim is a lightweight and powerful package; do not introduce scope creep while resolving conflicts.

@praisonai-triage-agent

praisonai-triage-agent Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

Claude finished @MervinPraison's task —— View job


Rebase & Conflict Resolution — PR #3398

  • Setup git identity & fetched latest main
  • Rebased onto origin/main (was ebd27167a)
  • Resolved conflicts (kept PR intent + newer main logic)
  • Ran targeted tests
  • Force-pushed with --force-with-lease

Files resolved

1 conflicted file: src/praisonai-agents/praisonaiagents/agent/handoff.py

main had independently landed an equivalent contextvars.ContextVar + copy-on-write _push_handoff/_pop_handoff refactor (same intent as this PR's Gap 2). The only conflict was the header comment/docstring block for _handoff_chain_var. I kept main's richer version and merged in a note documenting how it composes with copy-on-write to isolate parallel_handoffs sibling tasks. The Greptile-P1 commit (_run_one fresh per-task copy at handoff.py:1199) then re-applied cleanly on top — it now acts as defense-in-depth alongside the copy-on-write push/pop.

No conflicts in the other two PR files — both intents preserved after rebase:

  • memory_mixin.py:250,261 — per-instance uuid4().hex[:8] session_id suffix intact.
  • hooks/runner.py:127execute_async present; live consumers confirmed at chat_mixin.py:4644 and execution_mixin.py:1825.

No Agent params added; resolution stayed minimal (no scope creep).

Testing

  • Targeted suite (-k "handoff or hook"): 339 passed, 8 skipped.
  • Behavioral verification of all 3 fixes:
    • execute_async exists & is a coroutine ✓
    • Per-asyncio.Task chain isolation with a non-empty [Parent] chain under gather: children [['Parent','a'],['Parent','b'],['Parent','c']], parent restored to ['Parent']
  • 10 failures are all pre-existing / environment-only and unrelated to this PR's changed logic:
    • fastapi not installed (a2a push-notification tests)
    • the 5 known test_handoff_tool_policy.py Mock-setup failures (noted in the PR body; no references to handoff-chain internals)
    • Agent.__init__() got unexpected keyword 'db', verification_hooks deprecation, decomposition — none touch session_id / handoff ContextVar / execute_async.

Skipped

  • praisonai-rust, praisonai-ts — out of scope.
  • Pre-existing unrelated test collection/import errors left untouched (missing praisonai wrapper module, get_default_registry, resolve_output).

Branch force-pushed: 08b794b76...b3349e1ce.

--- • View jobclaude/issue-3397-20260726-0715

praisonai-triage-agent Bot and others added 2 commits July 27, 2026 08:42
…y hook crash (fixes #3397)

- memory_mixin: append per-instance uuid suffix to default session_id so
  same-named agents can no longer collide and bleed chat history
- handoff: replace threading.local() with contextvars.ContextVar so handoff
  chain/depth is isolated per asyncio.Task under parallel_handoffs
- hooks/runner: add HookRunner.execute_async alias so async retry-with-backoff
  no longer crashes with AttributeError

Co-authored-by: MervinPraison <MervinPraison@users.noreply.github.com>
parallel_handoffs runs children via asyncio.gather, which copies the
context per task. A copied ContextVar binding still references the same
parent list object, so when parallel_handoffs runs inside a non-empty
parent handoff chain, sibling tasks mutated a shared list and corrupted
cycle/depth tracking. Rebind each task's chain to a fresh per-task copy
at _run_one entry so siblings are fully isolated.

Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>
@MervinPraison MervinPraison added pipeline/blocked:ci Blocked: CI not green on HEAD pipeline/blocked:cooldown Blocked: post-push or @claude cooldown labels Jul 27, 2026
@praisonai-triage-agent praisonai-triage-agent Bot removed pipeline/blocked:ci Blocked: CI not green on HEAD pipeline/blocked:cooldown Blocked: post-push or @claude cooldown labels Jul 27, 2026
@MervinPraison
MervinPraison merged commit 4717e8e into main Jul 29, 2026
37 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pipeline/awaiting-merge-gate FINAL done; waiting for merge gate / CI pipeline/blocked:manual-review Blocked: requires manual review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

praisonaiagents core: session_id collisions, corrupted concurrent handoffs, and a broken async retry hook

1 participant