Skip to content

fix(agents-core): normalize instructions#2981

Merged
tim-inkeep merged 9 commits intomainfrom
fix/normalize-tool-prompts
Apr 3, 2026
Merged

fix(agents-core): normalize instructions#2981
tim-inkeep merged 9 commits intomainfrom
fix/normalize-tool-prompts

Conversation

@miles-kt-inkeep
Copy link
Copy Markdown
Contributor

No description provided.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 2, 2026

🦋 Changeset detected

Latest commit: 3de60d9

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 10 packages
Name Type
@inkeep/agents-core Patch
@inkeep/agents-manage-ui Patch
@inkeep/agents-api Patch
@inkeep/agents-cli Patch
@inkeep/agents-sdk Patch
@inkeep/agents-work-apps Patch
@inkeep/ai-sdk-provider Patch
@inkeep/create-agents Patch
@inkeep/agents-email Patch
@inkeep/agents-mcp Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 2, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agents-api Ready Ready Preview, Comment Apr 3, 2026 7:28pm
agents-docs Ready Ready Preview, Comment Apr 3, 2026 7:28pm
agents-manage-ui Ready Ready Preview, Comment Apr 3, 2026 7:28pm

Request Review

@pullfrog
Copy link
Copy Markdown
Contributor

pullfrog bot commented Apr 2, 2026

TL;DR — Works around a Doltgres JSON parser bug that rejects backslash-containing strings by providing a drop-in jsonb() column builder that encodes backslashes as a Unicode PUA placeholder (U+E000) before serialization and decodes them on read. Server instruction normalization in tools.ts is simplified to a plain passthrough.

Key changes

  • Add dolt-safe-jsonb.ts — Drizzle jsonb() drop-in with backslash encoding — Subclasses PgJsonb/PgJsonbBuilder to encode \ → U+E000 (and strip null bytes) before JSON.stringify and decode on read, preventing Doltgres from choking on escaped-backslash sequences.
  • Swap jsonb import in manage-schema.ts — Replaces the stock drizzle-orm/pg-core jsonb import with the custom dolt-safe-jsonb module so all manage-database JSONB columns get transparent backslash encoding.
  • Simplify discoverToolsFromServer in tools.ts — Removes the old null-byte stripping regex; instructions are now passed through with a ?? undefined coalesce since sanitization is handled at the JSONB layer.
  • Add tests for encode/decode roundtrip — New dolt-safe-jsonb.test.ts covering primitives, nested objects, arrays, null bytes, and the specific Composio instruction string that triggered the bug.
  • Add serverInstructions passthrough test — Verifies dbResultToMcpTool propagates instructions to capabilities without modification.
  • Add changeset — Patch bump for agents-core, agents-manage-ui, and agents-api.

Summary | 6 files | 9 commits | base: mainfix/normalize-tool-prompts


Doltgres-safe JSONB backslash encoding

Before: Doltgres rejected JSONB writes containing literal backslashes (e.g. \\n) due to an off-by-one error in its JSON escape-sequence state machine; a per-field regex in tools.ts tried to normalize just serverInstructions.
After: A custom DoltSafeJsonb Drizzle column subclass encodes all backslash characters as U+E000 and strips null bytes before serialization, then decodes on read — fixing the issue for every JSONB column in the manage schema transparently.

The implementation subclasses both PgJsonb and PgJsonbBuilder and exports a jsonb() function with the same API as Drizzle's built-in. manage-schema.ts swaps its jsonb import from drizzle-orm/pg-core to ./dolt-safe-jsonb, so all existing .$type<T>(), .notNull(), and .default() chains continue to work unchanged.

Why U+E000 and not a multi-character sentinel? U+E000 is the first character in Unicode's Private Use Area — it's essentially never present in real-world text, is a single code point (so replacement is cheap), and round-trips safely through both Postgres and Doltgres JSONB. If user data genuinely contains U+E000, it will be decoded as a backslash — an acceptable trade-off given PUA's rarity.

dolt-safe-jsonb.ts · manage-schema.ts · dolt-safe-jsonb.test.ts


Simplified server instruction passthrough

Before: discoverToolsFromServer called a normalizeServerInstructions helper that stripped null bytes and collapsed backslash-newline sequences with two regex passes.
After: Instructions are assigned directly from client.getInstructions() with a ?? undefined coalesce — null byte stripping and backslash handling are now the JSONB layer's responsibility.

tools.ts · dbResultToMcpTool.test.ts

Pullfrog  | View workflow run | Triggered by Pullfrog | Using Claude Opus𝕏

Copy link
Copy Markdown
Contributor

@pullfrog pullfrog bot left a comment

Choose a reason for hiding this comment

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

Clean, well-scoped fix. The normalizeServerInstructions extraction is a good refactor — consolidates null-byte stripping with the new backslash-newline normalization, and the regex correctly handles , \r , and \r variants. Test accurately constructs the problematic input via string concatenation and verifies both the return value and the persisted payload. No issues found.

Pullfrog  | View workflow run | Using Claude Opus𝕏

Copy link
Copy Markdown
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

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

PR Review Summary

(0) Total Issues | Risk: Low

💭 Consider (1) 💭

Inline Comments:

  • 💭 Consider: dbResultToMcpTool.test.ts:443 Additional test coverage for null byte removal and CRLF variants

✅ APPROVE

Summary: Clean, well-implemented fix that properly normalizes MCP server instructions. The new normalizeServerInstructions() function correctly handles both null byte removal (pre-existing) and the new backslash-newline conversion. The test coverage adequately demonstrates the new functionality works. One optional suggestion for additional test coverage is provided as a "Consider" item.

Note: The changeset bot flagged this PR as missing a changeset. Since this is a bug fix in agents-core that affects how server instructions are stored/displayed, a patch changeset may be appropriate per AGENTS.md guidelines.

Reviewers (2)
Reviewer Returned Main Findings Consider While You're Here Inline Comments Pending Recs Discarded
pr-review-standards 0 0 0 0 0 0 0
pr-review-tests 3 0 1 0 1 0 2
Total 3 0 1 0 1 0 2

Note: The tests reviewer identified test coverage gaps. The null byte test gap is pre-existing (was not tested before this PR either). The CRLF/CR variants are adequately demonstrated by the existing LF test pattern.

Discarded (2)
Location Issue Reason Discarded
dbResultToMcpTool.test.ts:400-444 Missing tests for CRLF/CR newline variants The regex pattern is straightforward and the existing LF test demonstrates it works. Additional tests would be redundant.
dbResultToMcpTool.test.ts:400-444 Missing explicit undefined test Already implicitly covered by existing tests that set mockMcpGetInstructions.mockReturnValue(undefined).

},
})
);
});
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.

💭 Consider Additional test coverage for edge cases

Issue: The test suite covers backslash-LF (\n) conversion but doesn't explicitly test null byte removal or other newline variants (CRLF, CR).

Why: The normalizeServerInstructions() function handles null bytes (pre-existing behavior) and three newline formats. While the current test demonstrates the pattern works, additional tests would make the contract clearer and catch regressions if the regex changes.

Fix: Consider adding a test for null byte removal:

it('removes null bytes from serverInstructions', async () => {
  const instructionsWithNullBytes = 'Some text\x00with\x00null bytes';
  const sanitizedInstructions = 'Some textwithnull bytes';

  mockMcpGetInstructions.mockReturnValue(instructionsWithNullBytes);
  // ... same db mock setup ...

  const result = await dbResultToMcpTool(makeGenericToolDbResult(), db);
  expect(result.capabilities?.serverInstructions).toBe(sanitizedInstructions);
});

This is optional — the current coverage is adequate for the new functionality.

@github-actions github-actions bot deleted a comment from claude bot Apr 2, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 2, 2026

Preview URLs

Use these stable preview aliases for testing this PR:

These point to the same Vercel preview deployment as the bot comment, but they stay stable and easier to find.

Raw Vercel deployment URLs

Copy link
Copy Markdown
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

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

PR Review Summary

(4) Total Issues | Risk: Medium

🟠⚠️ Major (2) 🟠⚠️

🟠 1) system Global prototype patch affects both manage and runtime databases

Issue: The prototype patch in dolt-safe-jsonb.ts modifies PgJsonb.prototype.mapToDriverValue and mapFromDriverValue globally. While the workaround is intended only for Doltgres (manage database), it affects all JSONB columns across both databases — including ~30+ JSONB columns in the runtime Postgres database.

Why: This introduces a hidden data transformation layer that: (1) applies unnecessary encode/decode cycles to runtime data that doesn't need them, (2) creates a collision risk if user data contains U+E000, and (3) establishes a precedent for global ORM patches to work around database-specific bugs. The runtime database stores arbitrary user-generated content (e.g., messages.content, artifacts.value) where exotic Unicode is more plausible than in configuration data.

Fix: Consider scoping the workaround to only the manage database:

  • Option A (Recommended): Use Drizzle's customType() to create a doltSafeJsonb column type used only in manage-schema.ts, leaving runtime-schema.ts using standard jsonb(). This requires updating ~40 JSONB column definitions in manage-schema but keeps the fix explicit and contained.
  • Option B: Accept the global patch but document the cross-database impact explicitly and add observability for U+E000 collisions.

Refs:

🟠 2) dolt-safe-jsonb.ts Side-effect import with undocumented ordering requirements

Issue: The prototype patch is applied as a side effect when dolt-safe-jsonb.ts is imported via import './dolt-safe-jsonb' in manage-client.ts. This creates implicit ordering dependencies.

Why:

  • Any code that directly imports from drizzle-orm/pg-core and uses PgJsonb before the side-effect import runs will get unpatched behavior
  • The test setup (__tests__/setup.ts) imports from test-manage-client.ts which does NOT import manage-client.ts, so the patch is not applied in tests — tests operate with different JSONB serialization behavior than production

Fix:

  1. Import ./dolt-safe-jsonb in the test setup file to ensure test/production parity
  2. Add a guard and explicit documentation about the import order requirement
  3. Consider adding a runtime check that warns if the patch hasn't been applied when manage database operations are attempted

Refs:

Inline Comments:

  • 🟠 Major: tools.ts:332 Null-byte stripping removed without clear justification
  • 🟠 Major: dolt-safe-jsonb.test.ts:3 Missing test for the actual prototype patch

💭 Consider (2) 💭

💭 1) dolt-safe-jsonb.ts:20-22 U+E000 collision is documented but not guarded

Issue: The comment acknowledges that U+E000 in user data will be "round-tripped as a backslash", which is silent data corruption.

Why: While U+E000 is in the Private Use Area and "essentially never used", this is a silent failure mode with no detection or warning.

Fix: Consider adding runtime detection (logging a warning) when U+E000 is encountered in data being decoded. This provides observability for the edge case without breaking functionality.

💭 2) dolt-safe-jsonb.test.ts:28 No test for object keys containing backslashes

Issue: The encodeBackslashes function uses Object.entries(value) which iterates over keys but does not encode keys themselves, only values. This appears intentional but is undocumented.

Fix: Add a test documenting this design decision:

it('does not encode backslashes in object keys (values only)', () => {
  const input = { 'key\\with\\backslash': 'value\\here' };
  const result = encodeBackslashes(input);
  expect(Object.keys(result as object)[0]).toBe('key\\with\\backslash');
  expect((result as any)['key\\with\\backslash']).toBe('value\uE000here');
});

🚫 REQUEST CHANGES

Summary: The new global PgJsonb prototype patch approach is clever and solves the Doltgres backslash parsing bug, but it introduces significant architectural concerns: (1) it affects both manage and runtime databases when only manage needs the workaround, (2) the test setup doesn't apply the patch creating production/test divergence, and (3) the null-byte stripping from the previous commit was dropped without explanation. Please clarify whether the global scope and null-byte removal are intentional, and consider scoping the fix to only the manage database.

Note: The changeset bot flagged this PR as missing a changeset. Since this is a bug fix in agents-core that affects how JSONB data is stored, a patch changeset is appropriate per AGENTS.md guidelines.

Discarded (1)
Location Issue Reason Discarded
dolt-safe-jsonb.test.ts:21 No test for Date/RegExp/Map/Set handling These types are not JSON-serializable and would fail at the JSON.stringify level before reaching this code. Edge case unlikely in practice.
Reviewers (3)
Reviewer Returned Main Findings Consider While You're Here Inline Comments Pending Recs Discarded
pr-review-standards 1 0 0 0 1 0 0
pr-review-architecture 3 2 1 0 0 0 0
pr-review-tests 3 0 1 0 1 0 1
Total 7 2 2 0 2 0 1

const rawServerInstructions = client.getInstructions();
// biome-ignore lint/suspicious/noControlCharactersInRegex: Intentionally matching control chars to remove them
const serverInstructions = rawServerInstructions?.replace(/\u0000/g, '');
const serverInstructions = client.getInstructions() ?? undefined;
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.

🟠 MAJOR Null-byte stripping removed

Issue: The previous commit added normalizeServerInstructions() which stripped null bytes via value.replaceAll(String.fromCharCode(0), ''). This refactoring removes that handling entirely.

Why: The original code had an explicit biome-ignore comment stating "Intentionally matching control chars to remove them" — suggesting null bytes from MCP servers were a known issue. If MCP servers can return null bytes in instructions, they will now pass through unfiltered to the database. This may cause issues depending on how Doltgres handles null bytes in JSON strings.

Fix: Verify whether null-byte stripping is still needed:

  • If the JSONB patch handles this → add a test verifying null bytes work correctly
  • If null bytes are still problematic → restore stripping, either inline or in encodeBackslashes()
  • If no longer needed → add a brief comment explaining why the removal is safe

Refs:

@@ -0,0 +1,102 @@
import { describe, expect, it } from 'vitest';
import { decodeBackslashes, encodeBackslashes } from '../../db/manage/dolt-safe-jsonb';

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.

🟠 MAJOR Missing test for the actual prototype patch

Issue: Tests cover the pure functions encodeBackslashes and decodeBackslashes, but don't verify that the PgJsonb.prototype patch (lines 53-62 of dolt-safe-jsonb.ts) works correctly.

Why: The prototype patching is the critical integration point that makes this fix work in production. Without a test, a regression in the patch (e.g., accidentally removing the import, incorrect this binding, wrong function call order) would go undetected while unit tests still pass.

Fix: Add a test that verifies the prototype patch:

import { PgJsonb } from 'drizzle-orm/pg-core';
import '../../db/manage/dolt-safe-jsonb'; // Apply the patch

describe('PgJsonb prototype patch', () => {
  it('encodes backslashes when serializing to driver', () => {
    const jsonb = new PgJsonb('test');
    const result = jsonb.mapToDriverValue({ path: 'C:\\Users' });
    expect(result).not.toContain('\\\\'); // No double-backslashes
    expect(result).toContain('\uE000');
  });

  it('decodes backslashes when reading from driver', () => {
    const jsonb = new PgJsonb('test');
    const result = jsonb.mapFromDriverValue({ path: 'C\uE000Users' });
    expect(result).toEqual({ path: 'C:\\Users' });
  });
});

Refs:

@github-actions github-actions bot deleted a comment from claude bot Apr 2, 2026
Copy link
Copy Markdown
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

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

PR Review Summary

(0) Total Issues | Risk: Low

This is a delta review scoped to changes since the previous review (commit 80a165fea780).

Prior Issues Resolution

All major issues from the previous review have been addressed:

Prior Issue Resolution
🟠 Global prototype patch affects both databases Fixed — Changed from PgJsonb.prototype modification to scoped DoltSafeJsonb class
🟠 Side-effect import with ordering requirements Fixed — Removed import './dolt-safe-jsonb' from manage-client.ts
🟠 Null-byte stripping removed Clarified — Null-byte handling was unrelated to the Doltgres backslash bug; removal is intentional
🟠 Missing test for prototype patch N/A — No longer using prototype patching; existing unit tests cover the encode/decode functions

Architecture Verification

The delta correctly scopes the Doltgres workaround:

Schema Import Source Effect
manage-schema.ts (Doltgres) ./dolt-safe-jsonb ✅ Backslash encoding enabled
runtime-schema.ts (Postgres) drizzle-orm/pg-core ✅ Standard behavior, unaffected

The class-based approach (DoltSafeJsonb extends PgJsonb) is cleaner than prototype patching:

  • Explicit opt-in via import
  • No global side effects
  • No ordering dependencies
  • Type-safe extension pattern (with documented @ts-expect-error for internal Drizzle API)

Notes

Fragility risk (acknowledged, acceptable): The implementation extends Drizzle's internal PgJsonbBuilder.build() method not in public types. Future Drizzle updates could break this. The existing test coverage (dolt-safe-jsonb.test.ts) will catch regressions.

Changeset reminder: The changeset bot flagged this PR. A patch changeset for @inkeep/agents-core is appropriate per AGENTS.md guidelines.


✅ APPROVE

Summary: The architectural concerns from the previous review have been thoroughly addressed. The new class-based approach properly scopes the Doltgres workaround to only the manage database, eliminating the global prototype patching and side-effect import issues. Clean fix. 🎉

Reviewers (2)
Reviewer Returned Main Findings Consider While You're Here Inline Comments Pending Recs Discarded
pr-review-standards 0 0 0 0 0 0 0
pr-review-architecture 0 0 0 0 0 0 0
Total 0 0 0 0 0 0 0

Note: Delta scope — reviewers verified prior issues are resolved; no new issues found.

@github-actions github-actions bot deleted a comment from claude bot Apr 2, 2026
Copy link
Copy Markdown
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

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

PR Review Summary

(0) Total Issues | Risk: Low

This is a delta review scoped to changes since the previous review (commit 52bb125faae7).

Delta Changes

Change Assessment
✅ Added changeset (smart-points-occur.md) Addresses prior reminder — correctly lists @inkeep/agents-core, @inkeep/agents-manage-ui, @inkeep/agents-api as patch bumps
✅ Added null-byte stripping to encodeBackslashes() Good addition — consolidates null-byte handling into the JSONB layer rather than at individual call sites
✅ Added test for null-byte stripping Proper test coverage for the new behavior

Null-Byte Handling Note

The delta adds .replaceAll('\0', '') to encodeBackslashes() on line 23 of dolt-safe-jsonb.ts. This is a cleaner solution than the original approach that was removed from tools.ts because:

  1. Centralized — null bytes are stripped for all JSONB columns in the manage database, not just serverInstructions
  2. No biome-ignore needed — the implementation uses the literal character '\0' instead of a regex, avoiding the linter warning
  3. Tested — covered by the new test case on line 40-42 of dolt-safe-jsonb.test.ts

Prior Issues Status

All issues from prior reviews remain resolved:

Prior Issue Status
Global prototype patch scope ✅ Resolved — class-based approach scopes to manage DB only
Side-effect import ordering ✅ Resolved — no side-effect imports
Null-byte stripping removed Now addressed — added to encodeBackslashes()
Missing changeset Now addressed — changeset added

✅ APPROVE

Summary: The delta since the last review consists of two improvements: adding the missing changeset and consolidating null-byte stripping into the JSONB encoding layer. Both changes are clean and well-tested. Ready to merge. 🎉

Reviewers (1)
Reviewer Returned Main Findings Consider While You're Here Inline Comments Pending Recs Discarded
orchestrator 0 0 0 0 0 0 0
Total 0 0 0 0 0 0 0

Note: Delta scope — minimal changes since last approval; no sub-reviewers needed.

@tim-inkeep
Copy link
Copy Markdown
Contributor

@miles-kt-inkeep it looks like it was maybe a doltgres deficiency that was causing the jsonb's to not get formatted right

@github-actions github-actions bot deleted a comment from claude bot Apr 2, 2026
@tim-inkeep tim-inkeep added this pull request to the merge queue Apr 3, 2026
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Apr 3, 2026
Copy link
Copy Markdown
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

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

PR Review Summary

(0) Total Issues | Risk: Low

This is a delta re-review scoped to changes since the previous review (commit ae64639e54d6).

Delta Analysis

Change Assessment
0e72a2c93 Merge branch 'main' into fix/normalize-tool-prompts ✅ Merge commit only — brings in unrelated changes from main

No changes to PR files since last review. The 6 files in this PR's scope remain unchanged:

  • .changeset/smart-points-occur.md
  • packages/agents-core/src/__tests__/data-access/dbResultToMcpTool.test.ts
  • packages/agents-core/src/__tests__/db/dolt-safe-jsonb.test.ts
  • packages/agents-core/src/data-access/manage/tools.ts
  • packages/agents-core/src/db/manage/dolt-safe-jsonb.ts
  • packages/agents-core/src/db/manage/manage-schema.ts

Prior Review Status

All issues from prior reviews remain resolved:

Prior Issue Status
Global prototype patch scope ✅ Resolved — class-based DoltSafeJsonb scopes to manage DB only
Side-effect import ordering ✅ Resolved — explicit import, no side effects
Null-byte stripping ✅ Resolved — consolidated in encodeBackslashes()
Changeset ✅ Present — patch bump for agents-core, agents-manage-ui, agents-api

✅ APPROVE

Summary: No changes to the PR's implementation since the last approval. The merge from main is clean and doesn't affect this fix. The Doltgres-safe JSONB workaround remains well-implemented, properly scoped to the manage database, and thoroughly tested. Ready to merge. 🎉

Reviewers (1)
Reviewer Returned Main Findings Consider While You're Here Inline Comments Pending Recs Discarded
orchestrator 0 0 0 0 0 0 0
Total 0 0 0 0 0 0 0

Note: Delta scope — no changes to PR files since last approval; sub-reviewers not needed.

Copy link
Copy Markdown
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

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

PR Review Summary

(0) Total Issues | Risk: Low

This is a delta re-review scoped to changes since the previous review (commit 0e72a2c93d85).

Delta Analysis

Change Assessment
3de60d9e4 Merge branch 'main' into fix/normalize-tool-prompts ✅ Merge commit only — brings in version bumps from main

No changes to PR implementation files since last review. The 6 files in this PR's scope remain unchanged:

  • .changeset/smart-points-occur.md
  • packages/agents-core/src/__tests__/data-access/dbResultToMcpTool.test.ts
  • packages/agents-core/src/__tests__/db/dolt-safe-jsonb.test.ts
  • packages/agents-core/src/data-access/manage/tools.ts
  • packages/agents-core/src/db/manage/dolt-safe-jsonb.ts
  • packages/agents-core/src/db/manage/manage-schema.ts

Implementation Summary

The PR implements a clean workaround for a Doltgres JSON parser bug:

Component Implementation
DoltSafeJsonb class Extends PgJsonb with encode/decode hooks for backslashes → U+E000
Scope Only manage-schema.ts (Doltgres) — runtime-schema.ts (Postgres) unaffected
Null-byte handling Consolidated in encodeBackslashes() — stripped for all manage JSONB columns
Test coverage 106 lines covering encode/decode, roundtrip, edge cases
Changeset ✅ Present — patch bump for agents-core, agents-manage-ui, agents-api

Prior Issues Status

All issues from prior reviews remain resolved:

Prior Issue Status
Global prototype patch scope ✅ Resolved — class-based DoltSafeJsonb scopes to manage DB only
Side-effect import ordering ✅ Resolved — explicit import, no side effects
Null-byte stripping ✅ Resolved — consolidated in encodeBackslashes()
Changeset ✅ Present

✅ APPROVE

Summary: No changes to the PR's implementation since the last approval. The merge from main is clean and only brings in version bumps from the Version Packages PR. The Doltgres-safe JSONB workaround remains well-implemented, properly scoped to the manage database, and thoroughly tested. Ready to merge. 🎉

Reviewers (1)
Reviewer Returned Main Findings Consider While You're Here Inline Comments Pending Recs Discarded
orchestrator 0 0 0 0 0 0 0
Total 0 0 0 0 0 0 0

Note: Delta scope — no changes to PR files since last approval; sub-reviewers not needed.

@github-actions github-actions bot deleted a comment from claude bot Apr 3, 2026
@tim-inkeep tim-inkeep added this pull request to the merge queue Apr 3, 2026
Merged via the queue into main with commit e91d67b Apr 3, 2026
25 of 26 checks passed
@tim-inkeep tim-inkeep deleted the fix/normalize-tool-prompts branch April 3, 2026 19:42
@itoqa
Copy link
Copy Markdown

itoqa bot commented Apr 3, 2026

Ito Test Report ✅

15 test cases ran. 1 additional finding, 14 passed.

Overall, 14 of 15 test cases passed, confirming stable MCP list/detail/deep-link navigation, correct prompt fallback and override precedence, reliable escape/backslash round-tripping, responsive long-content rendering, deterministic save behavior under refresh/multi-tab/rapid-submit stress, and strong security boundaries (stored XSS rendered inert and cross-project URL/API tampering denied). The key issue was one High-severity defect (LOGIC-3): after saving non-prompt MCP configuration fields, details/edit pages can become blank because the read path blocks on live MCP discovery (connect/tools) instead of promptly returning persisted data when discovery is slow or unavailable.

✅ Passed (14)
Category Summary Screenshot
Adversarial XSS payload in prompt rendered as inert text on details and edit views; no script execution observed. ADV-1
Adversarial Rapid repeated Save clicks and Enter submits remained stable with no malformed partial persisted prompt. ADV-2
Adversarial Tampering to another project returned UI 404 and API denial with no foreign tool/prompt disclosure; authorization boundary held. ADV-3
Adversarial Escape-flood prompt save and subsequent list/details/edit navigation stayed functional with no crash behavior. ADV-4
Edge Escape-dominated prompt payload was saved, rendered on details, and remained byte-stable after page refresh with no slash collapse or decode drift. EDGE-1
Edge A fresh DeepWiki MCP server showed a very long server-default prompt on desktop, then at 390x844 mobile after reload the prompt and controls remained readable and usable without overlap. EDGE-2
Edge Save+refresh and back/forward navigation preserved one coherent persisted prompt value (value-interrupt). EDGE-3
Edge Concurrent stale-tab writes stayed deterministic with last-write-wins (Value-B) after reload. EDGE-4
Logic Prompt with heavy backslashes/escaped markdown saved via UI, remained identical after refresh and edit reopen, and API read-after-write preserved escapes exactly after valid full-config PATCH. LOGIC-1
Logic Verified precedence cycle end-to-end: empty prompt showed server-default fallback, custom prompt save removed fallback and showed custom value, then clearing prompt restored fallback label/text consistently. LOGIC-2
Happy-path MCP list route rendered successfully; detail navigation and browser back remained stable; list API returned HTTP 200 with the seeded tool. ROUTE-1
Happy-path Details page correctly showed Prompt (server default) fallback and preserved a literal \n token; API detail readback matched persisted empty prompt plus server instructions. ROUTE-2
Happy-path On the edit route, Prompt placeholder displayed the required 'Leave empty to use server default:' prefix and truncated server-instructions preview with ellipsis, matching expected safe preview behavior. ROUTE-3
Happy-path Direct deep-link navigation to MCP server details and edit routes loaded correctly with the same saved tool data in an authenticated admin session. ROUTE-4
ℹ️ Additional Findings (1)

These findings are unrelated to the current changes but were observed during testing.

Category Summary Screenshot
Logic ⚠️ Saving non-prompt MCP config changes can lead to a blank details/edit experience because tool fetch depends on live discovery and can time out before rendering. LOGIC-3
⚠️ JSON config persistence unaffected for non-prompt MCP fields
  • What failed: The page flow can stall into blank main content after save instead of rehydrating persisted transport/tools/override values; expected behavior is a usable details/edit view with saved values visible.
  • Impact: Admin users can be blocked from viewing or editing affected MCP server configurations after a save. This prevents reliable verification of persisted settings and can interrupt configuration workflows.
  • Steps to reproduce:
    1. Open an MCP server edit page for an existing tool.
    2. Change non-prompt MCP fields such as transport type, active tool selection, and one tool override.
    3. Save the edits and navigate to the MCP server details page.
    4. Return to the edit route and verify whether persisted fields rehydrate normally.
  • Stub / mock context: A temporary local authentication shortcut was used during setup to keep MCP detail/edit routes reachable, but this failure itself was reproduced on real MCP discovery calls without route interception or canned API response stubs.
  • Code analysis: I inspected the details/edit UI fetch path and manage tool routes, then traced into MCP discovery. The API GET/PUT/PATCH tool handlers synchronously call dbResultToMcpTool, which performs live MCP discovery (connect + tools) before returning data; when discovery is slow or unreachable, this blocks the response path that details/edit rendering depends on.
  • Why this is likely a bug: The production read path for details/edit is directly coupled to external MCP discovery, so a transient discovery timeout can make core configuration screens unusable instead of returning persisted tool data promptly.

Relevant code:

packages/agents-core/src/data-access/manage/tools.ts (lines 324-334)

const client = new McpClient({
  name: tool.name,
  server: serverConfig,
});

await client.connect();

const serverTools = await client.tools();
const serverInstructions = client.getInstructions() ?? undefined;

await client.disconnect();

packages/agents-core/src/data-access/manage/tools.ts (lines 437-445)

const discoveryResult = await discoverToolsFromServer(
  dbResult,
  credentialReference,
  credentialStoreRegistry,
  userId
);
availableTools = discoveryResult.tools;
serverInstructions = discoveryResult.serverInstructions;
status = 'healthy';

agents-api/src/domains/manage/routes/tools.ts (lines 181-183)

return c.json({
  data: await dbResultToMcpTool(tool, db, credentialStores, undefined, userId),
});

Commit: 3de60d9

View Full Run


Tell us how we did: Give Ito Feedback

amikofalvy added a commit that referenced this pull request Apr 3, 2026
…JSONB data

Companion to #2981 (dolt-safe-jsonb write fix). That PR encodes backslashes
as U+E000 on new writes, but existing data still has raw backslashes that
can trigger Doltgres JSON parser errors on queries. This script re-encodes
all JSONB columns across all Doltgres branches.

Three modes: --scan (read-only audit), default (dry run), --apply (write).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
amikofalvy added a commit that referenced this pull request Apr 5, 2026
…JSONB data

Companion to #2981 (dolt-safe-jsonb write fix). That PR encodes backslashes
as U+E000 on new writes, but existing data still has raw backslashes that
can trigger Doltgres JSON parser errors on queries. This script re-encodes
all JSONB columns across all Doltgres branches.

Three modes: --scan (read-only audit), default (dry run), --apply (write).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
github-merge-queue bot pushed a commit that referenced this pull request Apr 5, 2026
…B data (#3008)

* fix: add migration script to encode backslashes in existing Doltgres JSONB data

Companion to #2981 (dolt-safe-jsonb write fix). That PR encodes backslashes
as U+E000 on new writes, but existing data still has raw backslashes that
can trigger Doltgres JSON parser errors on queries. This script re-encodes
all JSONB columns across all Doltgres branches.

Three modes: --scan (read-only audit), default (dry run), --apply (write).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style: apply biome formatting to migration script

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add repair script for Doltgres-corrupted JSONB rows

Handles rows where Doltgres mangled backslash escape sequences so badly
that the stored JSON is structurally invalid (throws "Bad escaped character"
on read).

Two repair strategies:
- tools.capabilities → NULL (auto-rediscovered on MCP health check)
- data_components.props/render → attempt JSON repair + U+E000 encoding

Modes: --scan (audit), default (dry run), --apply (write fixes)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style: apply biome formatting to repair script

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: handle pool idle timeout to prevent ETIMEDOUT crash on exit

Add idleTimeoutMillis and swallow pool-level errors so the script
exits cleanly after processing all branches on remote Doltgres.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add --dump mode to write full corrupt JSONB content to file

Dumps the complete raw content of each corrupt column to a text file
for review. Deduplicates across branches so each unique row appears once.

Usage: node scripts/fix-doltgres-corrupt-jsonb.mjs --dump [--out file.txt]

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style: auto-format with biome

* fix: make --dump an add-on flag that works with --scan

--dump now combines with --scan (or runs alone as implicit scan):
  --scan --dump  → scan console output + full content dump to file
  --dump         → same (implies scan)
  --dump --out f → custom output path

Deduplicates across branches so each unique corrupt row appears once.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: show string diff in dry-run mode for repair preview

Dry run now displays a unified-style diff for each corrupt column:
- NULL strategy: shows current content → NULL
- Repair strategy: shows current content → repaired JSON

Control chars are made visible in the diff output.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style: auto-format with biome

* feat: colorize dry-run diff output

Red badge + red text for removed lines, green badge + green text
for added lines, dimmed context. Much easier to spot changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style: auto-format with biome

* feat: character-level diff highlighting in dry-run mode

Changed characters within a line now get background-colored:
- Deleted chars: white on red background
- Inserted chars: black on green background
- Unchanged prefix/suffix stays normal red/green

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: pretty-print both sides of dry-run diff for readable output

Both the corrupt and repaired JSON are now pretty-printed with matching
structure before diffing. This means only the actual content changes
(backslash escaping fixes) show up as diff lines, not JSON formatting
noise from compact vs pretty-printed serialization.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: replace line-level diff with focused inline change hunks

Instead of showing huge single-line JSON diffs, the dry-run now:
- Finds each individual character-level change
- Shows it as a numbered hunk with 50 chars of surrounding context
- Highlights the exact deleted/inserted chars with background color

Example output:
  2 change(s):
  Change 1 at position 7253:
    - ...${country.country}\[HIGHLIGHTED:\n]Happiness...
    + ...${country.country}\[HIGHLIGHTED:\\n]Happiness...

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: show literal control chars as ␊ ␉ ␍ to distinguish from \n \t \r

Before: both sides showed \\n — impossible to see the difference.
Now: corrupt side shows ␊ (literal newline), repaired side shows \\n
(escape sequence). The actual change is immediately obvious.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add comprehensive run instructions to both migration scripts

Both scripts now include full background context, prerequisites,
step-by-step example commands, and flag reference in their headers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: remove unused backslash encoding script

Production scan found 0 rows needing encoding. The actual problem
(corrupt JSON) is handled by fix-doltgres-corrupt-jsonb.mjs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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