Skip to content

fix(server): handle action_input JSONB-string shape + write JSONB objects for new runs#877

Merged
buremba merged 2 commits into
mainfrom
fix/runs-action-input-jsonb-shape
May 18, 2026
Merged

fix(server): handle action_input JSONB-string shape + write JSONB objects for new runs#877
buremba merged 2 commits into
mainfrom
fix/runs-action-input-jsonb-shape

Conversation

@buremba
Copy link
Copy Markdown
Member

@buremba buremba commented May 18, 2026

Summary

Third bug in the Phase 5 chain. Workers correctly POST snapshots (post PR #874), but the gateway's verifier 403's every one because runs.action_input is stored as a JSONB string (double-encoded) — action_input ->> 'agentId' returns NULL.

Root cause

runs-queue.ts:309: JSON.stringify(data) bound to a $4::jsonb parameter → Postgres stored the JSONB as a string scalar, not a JSONB object. Every chat_message run since the dawn of time has been wrong-shape.

Fix

  • Verifier (transcript-routes.ts): CASE jsonb_typeof(action_input) handles both shapes. String rows unwrap via (action_input #>> '{}')::jsonb first; object rows use direct ->>. Crossover-safe.
  • Dispatch (runs-queue.ts): sql.json(data) replaces JSON.stringify so postgres-js sends a real JSONB object going forward.

Verification

After merge + image roll, Telegram message via @embeddedlobu2bot should produce a row in agent_transcript_snapshot (currently 0 rows since Phase 5).

Test plan

  • make typecheck clean
  • CI green
  • Real Telegram message → snapshot row appears

Summary by CodeRabbit

  • Bug Fixes

    • Fixed snapshot authorization so valid snapshots from legacy-inflight records are accepted.
    • Ensured queued action payloads are persisted as structured JSON to avoid mismatches.
  • Tests

    • Added regression tests covering snapshot authorization and end-to-end persistence for legacy and current data shapes.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 18, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7e1cb080-5644-4a0c-8aef-85d17b478f19

📥 Commits

Reviewing files that changed from the base of the PR and between f8ae149 and 4adff11.

📒 Files selected for processing (5)
  • packages/owletto
  • packages/server/src/gateway/__tests__/agent-transcript-snapshot.test.ts
  • packages/server/src/gateway/__tests__/runs-queue-integration.test.ts
  • packages/server/src/gateway/gateway/transcript-routes.ts
  • packages/server/src/gateway/infrastructure/queue/runs-queue.ts

📝 Walkthrough

Walkthrough

This PR fixes the runs queue to properly store action_input as a JSONB object and updates transcript snapshot authorization to extract agentId/conversationId from action_input whether it is a modern object or a legacy double-encoded JSONB string.

Changes

Action input JSONB shape fixes

Layer / File(s) Summary
Queue storage fix
packages/server/src/gateway/infrastructure/queue/runs-queue.ts, packages/server/src/gateway/__tests__/runs-queue-integration.test.ts
RunsQueue.send() now uses sql.json() to store action_input as a JSONB object instead of a double-encoded string. Regression test verifies jsonb_typeof(action_input) is "object" and ->> extraction returns expected agentId and conversationId.
Authorization backward compatibility
packages/server/src/gateway/gateway/transcript-routes.ts, packages/server/src/gateway/__tests__/agent-transcript-snapshot.test.ts
isRunOwnedByJwtScope now uses jsonb_typeof() with CASE expressions to extract agentId and conversationId from action_input for both object and legacy string shapes. Integration test seeds both shapes and verifies /snapshot accepts both and still rejects mismatched JWT scopes (403).
Subproject pointer update
packages/owletto
Updates the packages/owletto subproject reference SHA.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • lobu-ai/lobu#873: Modifies isRunOwnedByJwtScope logic to use denormalized columns instead of parsing action_input; overlaps on transcript-routes authorization strategy.
  • lobu-ai/lobu#870: Also adjusts isRunOwnedByJwtScope and runs-queue setup for correct (org, agentId, conversationId) derivation across historical data shapes.
  • lobu-ai/lobu#865: Modifies transcript snapshot authorization in transcript-routes.ts around isRunOwnedByJwtScope and related queue payload shaping for run integrity.

Poem

🐰
I hopped through rows both old and new,
Unwrapped a string to find the truth;
sql.json() now plants the seed,
CASE logic reads both shapes with speed,
Happy runs, secure and snug—hoppity-hooray!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: fixing JSONB-string handling in the verifier and updating the dispatch to write proper JSONB objects.
Description check ✅ Passed The description provides comprehensive context (summary, root cause, fixes, verification steps) but the test plan checkboxes are incomplete—only typecheck is marked done, while CI and manual verification are pending.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/runs-action-input-jsonb-shape

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

@codecov-commenter
Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

buremba added 2 commits May 18, 2026 16:36
…ects for new runs

Live prod bug — third in the Phase 5 chain. Snapshot mode is default,
worker correctly POSTs to /worker/transcript/snapshot with the right
runId (PR #874), but the gateway's isRunOwnedByJwtScope verifier
rejects with 403 on every call because `runs.action_input` is stored
as a JSONB **string** (double-encoded), not a JSONB object. The
verifier's `->> 'agentId'` returns NULL on a JSONB string, so the
scope comparison fails.

Root cause traced to runs-queue.ts:309 — `JSON.stringify(data)` was
bound to a `$4::jsonb` parameter, which Postgres ingested as a JSONB
string scalar. Fixed by passing the object through postgres-js's
`sql.json()` helper so the driver sends a proper JSONB object.

Two-part fix:
- Verifier (transcript-routes.ts): CASE jsonb_typeof to handle both
  shapes — object rows use direct `->>`, string rows unwrap via
  `(action_input #>> '{}')::jsonb`. New rows post fix always take the
  'object' branch; legacy in-flight string rows authorize correctly
  during the deploy crossover window.
- Dispatch (runs-queue.ts): write JSONB objects directly via sql.json
  going forward. New chat_message / task rows store proper objects.

Tests cover both shapes in the verifier and the new dispatch shape.
@buremba buremba force-pushed the fix/runs-action-input-jsonb-shape branch from 368a398 to 4adff11 Compare May 18, 2026 15:36
@buremba buremba merged commit 683481c into main May 18, 2026
@buremba buremba deleted the fix/runs-action-input-jsonb-shape branch May 18, 2026 15:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants