Skip to content

Conversation

@deepshekhardas
Copy link

@deepshekhardas deepshekhardas commented Feb 2, 2026

Summary

Fixes #1510

Problem

Logging objects with keys containing dots (e.g. { 'Key 0.002mm': 31.4 }) resulted in incorrect nested object structure in the UI/logs (e.g. { 'Key 0': { '002mm': 31.4 } }) because dots were treated as separators during unflattening.

Fix

Implemented key escaping in \ lattenAttributes.ts:

  • \ lattenAttributes\ now escapes dots in keys (e.g. .\ -> \\.)
  • \unflattenAttributes\ now respects escaped dots when splitting keys

This ensures dotted keys are preserved as single keys during the flatten/unflatten cycle.


Open with Devin

@changeset-bot
Copy link

changeset-bot bot commented Feb 2, 2026

🦋 Changeset detected

Latest commit: a8fa6a5

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

This PR includes changesets to release 28 packages
Name Type
@trigger.dev/core Patch
@trigger.dev/build Patch
trigger.dev Patch
@trigger.dev/python Patch
@trigger.dev/redis-worker Patch
@trigger.dev/schema-to-json Patch
@trigger.dev/sdk Patch
@internal/cache Patch
@internal/clickhouse Patch
@internal/redis Patch
@internal/replication Patch
@internal/run-engine Patch
@internal/schedule-engine Patch
@internal/testcontainers Patch
@internal/tracing Patch
@internal/tsql Patch
@internal/zod-worker Patch
d3-chat Patch
references-d3-openai-agents Patch
references-nextjs-realtime Patch
references-realtime-hooks-test Patch
references-realtime-streams Patch
references-telemetry Patch
@internal/sdk-compat-tests Patch
@trigger.dev/react-hooks Patch
@trigger.dev/rsc Patch
@trigger.dev/database Patch
@trigger.dev/otlp-importer 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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 2, 2026

Walkthrough

This PR fixes logging behavior for objects containing dotted keys in their attribute names. The changes introduce helper functions to escape dots and backslashes in keys and parse dot-notated paths with escape support. The flattenAttributes function signature is updated to include an optional maxAttributeCount parameter, and the flattening logic is adjusted to use the new escapeKey helper for proper key handling. The unflattenAttributes function now uses the new splitPath helper instead of a naive split operation to correctly handle escaped separators. Tests are updated with formatting adjustments and Vitest imports.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description addresses the linked issue, explains the problem and fix, but is missing the Checklist section and Testing/Changelog sections from the required template. Complete the PR description by including the Checklist, Testing details, and Changelog sections from the required template to ensure full compliance.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and specifically describes the main change: escaping dotted keys in flattenAttributes to fix log rendering, directly matching the changeset and code modifications.
Linked Issues check ✅ Passed The PR successfully implements the required fix for issue #1510: escaping dots in keys during flattening and respecting escaped dots during unflattening to prevent incorrect nested object rendering in logs.
Out of Scope Changes check ✅ Passed All changes are scoped to flattenAttributes.ts, its tests, and a changelog entry, directly addressing the requirement to escape/unescape dots in keys for logging; no out-of-scope modifications detected.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

View issue and 3 additional flags in Devin Review.

Open in Devin Review

Choose a reason for hiding this comment

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

🔴 Map keys containing dots are not escaped, causing incorrect unflattening

The PR fixes dotted keys for regular objects by using escapeKey() at line 233, but Map keys at line 150 are not escaped.

Click to expand

How the bug is triggered

When a Map has a key containing a dot (e.g., "Key 0.002mm"), the current code builds the path without escaping:

// Line 150 - Map keys are NOT escaped
this.#processValue(value, `${prefix || "map"}.${keyStr}`, depth);

Compare to regular object keys at line 233:

// Line 233 - Object keys ARE escaped
const newPrefix = `${prefix ? `${prefix}.` : ""}${Array.isArray(obj) ? `[${key}]` : escapeKey(key)}`;

Actual vs Expected behavior

Actual: Map with key "Key 0.002mm" produces flattened path myMap.Key 0.002mm, which splitPath() splits into ['myMap', 'Key 0', '002mm'], resulting in nested object { myMap: { 'Key 0': { '002mm': value } } }

Expected: Path should be myMap.Key 0\.002mm, which splitPath() correctly splits into ['myMap', 'Key 0.002mm'], preserving the original structure { myMap: { 'Key 0.002mm': value } }

Impact

This causes data corruption during the flatten/unflatten cycle for Maps with dotted keys - the exact same issue the PR was trying to fix, but only fixed for regular objects.

(Refers to line 150)

Recommendation: Escape the Map key using escapeKey(): this.#processValue(value, \${prefix || "map"}.${escapeKey(keyStr)}`, depth);`

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@vibe-kanban-cloud
Copy link

Review Complete

Your review story is ready!

View Story

Comment !reviewfast on this PR to re-generate the story.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/core/src/v3/utils/flattenAttributes.ts (2)

144-153: ⚠️ Potential issue | 🟠 Major

Map keys containing dots are not escaped.

Map key handling at line 150 directly interpolates the key without escaping. If a Map contains a key like "a.b", it will flatten to "map.a.b" and unflatten incorrectly as a nested structure {map: {a: {b: ...}}}.

This is inconsistent with the fix applied to regular object keys at line 233.

🐛 Proposed fix to escape Map keys
       // Use the key directly if it's a string, otherwise convert it
       const keyStr = typeof key === "string" ? key : String(key);
-      this.#processValue(value, `${prefix || "map"}.${keyStr}`, depth);
+      this.#processValue(value, `${prefix || "map"}.${escapeKey(keyStr)}`, depth);

38-47: ⚠️ Potential issue | 🟡 Minor

Add tests for keys containing dots to verify round-trip escaping.

The implementation includes escapeKey() to handle dots in keys (line 8) and splitPath() to unescape during reconstruction (line 12), but the test suite lacks explicit verification. Add a round-trip test to ensure keys with dots (e.g., "Key 0.002mm") are properly escaped during flattening and correctly reconstructed during unflattening.

🤖 Fix all issues with AI agents
In `@packages/core/src/v3/utils/flattenAttributes.ts`:
- Around line 12-36: The splitPath function can drop a trailing unescaped
backslash because isEscaped may be true at loop end; fix it by detecting when
isEscaped is still true after the loop in splitPath and append a literal
backslash (e.g., add "\\" to current) before pushing the final segment, so paths
like "key\\" preserve the trailing backslash instead of silently losing it.
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1ccb8c1 and a8fa6a5.

📒 Files selected for processing (3)
  • .changeset/fix-dotted-keys-logging.md
  • packages/core/src/v3/utils/flattenAttributes.ts
  • packages/core/test/flattenAttributes.test.ts
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{ts,tsx}

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

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

**/*.{ts,tsx}: Always import tasks from @trigger.dev/sdk, never use @trigger.dev/sdk/v3 or deprecated client.defineJob pattern
Every Trigger.dev task must be exported and have a unique id property with no timeouts in the run function

Files:

  • packages/core/src/v3/utils/flattenAttributes.ts
  • packages/core/test/flattenAttributes.test.ts
{packages/core,apps/webapp}/**/*.{ts,tsx}

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

Use zod for validation in packages/core and apps/webapp

Files:

  • packages/core/src/v3/utils/flattenAttributes.ts
  • packages/core/test/flattenAttributes.test.ts
**/*.{ts,tsx,js,jsx}

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

Use function declarations instead of default exports

Import from @trigger.dev/core using subpaths only, never import from root

Files:

  • packages/core/src/v3/utils/flattenAttributes.ts
  • packages/core/test/flattenAttributes.test.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)

**/*.ts: When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs
Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)
When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries

Files:

  • packages/core/src/v3/utils/flattenAttributes.ts
  • packages/core/test/flattenAttributes.test.ts
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier before committing

Files:

  • packages/core/src/v3/utils/flattenAttributes.ts
  • packages/core/test/flattenAttributes.test.ts
{packages,integrations}/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Add a changeset when modifying any public package in packages/* or integrations/* using pnpm run changeset:add

Files:

  • packages/core/src/v3/utils/flattenAttributes.ts
  • packages/core/test/flattenAttributes.test.ts
**/*.{test,spec}.{ts,tsx}

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

Use vitest for all tests in the Trigger.dev repository

Files:

  • packages/core/test/flattenAttributes.test.ts
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.test.{ts,tsx,js,jsx}: Test files should live beside the files under test and use descriptive describe and it blocks
Tests should avoid mocks or stubs and use the helpers from @internal/testcontainers when Redis or Postgres are needed
Use vitest for running unit tests

**/*.test.{ts,tsx,js,jsx}: Use vitest exclusively for testing and never mock anything - use testcontainers instead
Place test files next to source files with naming pattern: source file (e.g., MyService.ts) → MyService.test.ts

Files:

  • packages/core/test/flattenAttributes.test.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use testcontainers helpers (redisTest, postgresTest, containerTest) from @internal/testcontainers for Redis/PostgreSQL testing instead of mocks

Files:

  • packages/core/test/flattenAttributes.test.ts
🧠 Learnings (5)
📚 Learning: 2026-01-15T10:48:02.687Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-15T10:48:02.687Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Use vitest for running unit tests

Applied to files:

  • packages/core/test/flattenAttributes.test.ts
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to **/*.{test,spec}.{ts,tsx} : Use vitest for all tests in the Trigger.dev repository

Applied to files:

  • packages/core/test/flattenAttributes.test.ts
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Use vitest exclusively for testing and never mock anything - use testcontainers instead

Applied to files:

  • packages/core/test/flattenAttributes.test.ts
📚 Learning: 2026-01-15T10:48:02.687Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-15T10:48:02.687Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Test files should live beside the files under test and use descriptive `describe` and `it` blocks

Applied to files:

  • packages/core/test/flattenAttributes.test.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use logger methods (debug, log, info, warn, error) from `trigger.dev/sdk/v3` for structured logging in tasks

Applied to files:

  • .changeset/fix-dotted-keys-logging.md
🔇 Additional comments (8)
.changeset/fix-dotted-keys-logging.md (1)

1-5: LGTM!

The changeset correctly documents the patch for @trigger.dev/core and clearly describes the fix for issue #1510.

packages/core/src/v3/utils/flattenAttributes.ts (4)

8-10: LGTM!

The escape order is correct—backslashes are escaped first to prevent double-escaping when dots are subsequently escaped.


228-246: LGTM!

The core fix correctly applies escapeKey to object property keys while preserving bracket notation for array indices. This properly addresses the dotted-key issue.


312-329: LGTM!

Using splitPath instead of naive split(".") correctly handles escaped separators, ensuring round-trip consistency with the escaping applied during flattening.


54-57: LGTM!

Minor formatting adjustment in the constructor body.

packages/core/test/flattenAttributes.test.ts (3)

1-1: LGTM!

Vitest imports added correctly per coding guidelines.


300-322: LGTM!

Formatting adjustments for empty function bodies are consistent throughout the file.


416-433: LGTM!

Consistent formatting for empty callbacks in Promise tests.

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

Comment on lines +12 to +36
function splitPath(path: string): string[] {
const parts: string[] = [];
let current = "";
let isEscaped = false;

for (let i = 0; i < path.length; i++) {
const char = path[i];

if (isEscaped) {
current += char;
isEscaped = false;
} else if (char === "\\") {
isEscaped = true;
} else if (char === ".") {
parts.push(current);
current = "";
} else {
current += char;
}
}

parts.push(current);

return parts;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Minor edge case: trailing backslash is silently dropped.

If a path ends with an unescaped backslash (e.g., "key\\"), the isEscaped flag is set but no subsequent character exists to append. The trailing backslash is silently dropped.

This is unlikely to occur in practice since escapeKey only produces escaped sequences like \\\\ (for literal backslash) or \\. (for literal dot), never a dangling backslash. However, for defensive completeness, you could append the backslash if isEscaped is still true after the loop.

🛡️ Optional defensive fix
   parts.push(current);

+  if (isEscaped) {
+    // Handle trailing backslash (shouldn't occur with proper escaping)
+    parts[parts.length - 1] += "\\";
+  }
+
   return parts;
 }
🤖 Prompt for AI Agents
In `@packages/core/src/v3/utils/flattenAttributes.ts` around lines 12 - 36, The
splitPath function can drop a trailing unescaped backslash because isEscaped may
be true at loop end; fix it by detecting when isEscaped is still true after the
loop in splitPath and append a literal backslash (e.g., add "\\" to current)
before pushing the final segment, so paths like "key\\" preserve the trailing
backslash instead of silently losing it.

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.

[TRI-4123] Logging objects with keys with periods in doesn't render properly in the UI

1 participant