Skip to content

fix(copilot): mount input tables with display-name CSV headers, not column IDs#5121

Merged
TheodoreSpeaks merged 2 commits into
stagingfrom
fix/code-mount-table-column-names
Jun 17, 2026
Merged

fix(copilot): mount input tables with display-name CSV headers, not column IDs#5121
TheodoreSpeaks merged 2 commits into
stagingfrom
fix/code-mount-table-column-names

Conversation

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator

Summary

  • The function_execute tool mounted input tables as CSV with both display-name headers AND stable column IDs (col_…), with values only under the ID headers — display-name columns came out empty, silently breaking any code reading by header name (e.g. a chart that bucketed everything into one empty group)
  • Rewrote the mount CSV builder to mirror the export path: display-name headers only, values read by getColumnId, reusing the shared toCsvRow/formatCsvValue/neutralizeCsvFormula serializers (also adds CSV-formula-injection safety)
  • Added unit tests covering id-keyed and legacy name-keyed rows

Type of Change

  • Bug fix

Testing

Tested manually + new unit tests (2 passing). bun run lint and bun run check:api-validation:strict pass.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile review

@cursor

cursor Bot commented Jun 17, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes copilot sandbox table CSV generation on the inline query path; behavior shifts from broken name-keyed reads to export-aligned output, with snapshot mounts unchanged in this diff.

Overview
Fixes inline CSV mounting for copilot function_execute input tables when row data is stored under stable column ids (col_…) but headers were built from a mix of names and extra keys—so scripts reading by display name saw empty columns.

The mount builder now matches the table export path: header row uses display names only (with formula neutralization), cell values are read via getColumnId, and rows are serialized with shared toCsvRow / formatCsvValue helpers (including proper quoting and CSV-injection handling). Legacy name-keyed columns still work through the id fallback.

resolveInputFiles is exported so tests can cover the mount behavior; unit tests were updated for id-keyed mocks and expanded for multi-column CSV, commas in values, and legacy schemas.

Reviewed by Cursor Bugbot for commit 2cadfc2. Bugbot is set up for automated code reviews on this repo. Configure here.

@vercel

vercel Bot commented Jun 17, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Jun 17, 2026 9:38pm

Request Review

@greptile-apps

greptile-apps Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a silent data bug in the copilot function_execute tool where table CSV mounts produced empty display-name columns because values were only written under stable col_… column-ID headers while display-name headers were emitted side-by-side with no data. The fix rewrites the CSV builder to emit display-name-only headers and look up values by getColumnId, mirroring the existing export path.

  • Bug fix — CSV header generation now uses column.name exclusively; values are keyed by getColumnId(column) (falls back to name for legacy id-less columns), so chart/pivot consumers reading by header name will receive their data correctly.
  • Shared serializerstoCsvRow / formatCsvValue / neutralizeCsvFormula are reused from export-format.ts, adding CSV-formula-injection safety that was absent in the original ad-hoc serializer.
  • Tests — two new unit tests cover the id-keyed path (including comma-in-value quoting) and the legacy name-keyed fallback path; resolveInputFiles is exported to make this possible.

Confidence Score: 5/5

Safe to merge — the change is a targeted CSV serialization fix with no side effects on other paths.

The rewrite is small and self-contained. rows.rows and row.data are typed as non-nullable in QueryResult/TableRow, so the removed ?? [] / || {} guards were defensive noise rather than real protection. The shared serializers are well-tested upstream, and the two new unit tests directly verify the corrected header/value behavior and the legacy name-keyed fallback. No regressions were introduced on the existing export path since those code paths are untouched.

No files require special attention.

Important Files Changed

Filename Overview
apps/sim/lib/copilot/tools/handlers/function-execute.ts Core fix: replaces ad-hoc CSV builder with shared serializers and corrects header/value key mismatch; resolveInputFiles exported for testability; types confirm rows.rows and row.data are non-nullable so the removed ?? [] / `
apps/sim/lib/copilot/tools/handlers/function-execute.test.ts New test file covering id-keyed rows (with comma-in-value quoting assertion) and legacy name-keyed fallback; mocks are correctly scoped with vi.hoisted; no issues found.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Copilot
    participant resolveInputFiles
    participant queryRows
    participant getColumnId
    participant formatCsvValue/toCsvRow

    Copilot->>resolveInputFiles: "inputTables=[tbl_123]"
    resolveInputFiles->>queryRows: "queryRows(table, {})"
    queryRows-->>resolveInputFiles: "{ rows: [{id, data:{col_name:"Ada",...}}] }"
    Note over resolveInputFiles: Build header row using display names only
    resolveInputFiles->>formatCsvValue/toCsvRow: toCsvRow([neutralizeCsvFormula(col.name)])
    formatCsvValue/toCsvRow-->>resolveInputFiles: "name,company"
    Note over resolveInputFiles: Build each data row using getColumnId for lookup
    resolveInputFiles->>getColumnId: "getColumnId({id:"col_name", name:"name"})"
    getColumnId-->>resolveInputFiles: "col_name"
    resolveInputFiles->>formatCsvValue/toCsvRow: toCsvRow([formatCsvValue(row.data["col_name"])])
    formatCsvValue/toCsvRow-->>resolveInputFiles: "Ada,Analytical Engine"
    resolveInputFiles-->>Copilot: "[{path:"/home/user/tables/tbl_123.csv", content:"name,company\nAda,Analytical Engine"}]"
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Copilot
    participant resolveInputFiles
    participant queryRows
    participant getColumnId
    participant formatCsvValue/toCsvRow

    Copilot->>resolveInputFiles: "inputTables=[tbl_123]"
    resolveInputFiles->>queryRows: "queryRows(table, {})"
    queryRows-->>resolveInputFiles: "{ rows: [{id, data:{col_name:"Ada",...}}] }"
    Note over resolveInputFiles: Build header row using display names only
    resolveInputFiles->>formatCsvValue/toCsvRow: toCsvRow([neutralizeCsvFormula(col.name)])
    formatCsvValue/toCsvRow-->>resolveInputFiles: "name,company"
    Note over resolveInputFiles: Build each data row using getColumnId for lookup
    resolveInputFiles->>getColumnId: "getColumnId({id:"col_name", name:"name"})"
    getColumnId-->>resolveInputFiles: "col_name"
    resolveInputFiles->>formatCsvValue/toCsvRow: toCsvRow([formatCsvValue(row.data["col_name"])])
    formatCsvValue/toCsvRow-->>resolveInputFiles: "Ada,Analytical Engine"
    resolveInputFiles-->>Copilot: "[{path:"/home/user/tables/tbl_123.csv", content:"name,company\nAda,Analytical Engine"}]"
Loading

Reviews (1): Last reviewed commit: "fix(copilot): mount input tables with di..." | Re-trigger Greptile

@greptile-apps

greptile-apps Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a silent data-loss bug in the copilot function_execute table mount: rows stored with stable column IDs (col_…) were never accessible under their display-name CSV headers because the old builder looked up values by display name rather than by the stored key.

  • function-execute.ts: Replaces the ad-hoc union-key CSV builder with display-name headers + getColumnId-keyed value lookups, reusing the shared toCsvRow / formatCsvValue / neutralizeCsvFormula serializers from the export path (also closes a CSV-formula-injection gap and makes the output byte-identical with table exports).
  • function-execute.test.ts: New Vitest file covering both modern id-keyed rows and the legacy name-keyed fallback path; @/lib/table/export-format and @/lib/table/column-keys are intentionally not mocked so the real serializers are exercised end-to-end.

Confidence Score: 5/5

Safe to merge — the change is tightly scoped to the CSV builder inside the table mount path and does not touch any other execution logic.

The fix is straightforward and well-validated: QueryResult.rows is typed as a required TableRow[] (never optional) and TableRow.data is a required RowData (non-nullable), so removing the old defensive ?? [] and || {} guards is correct. The real shared serializers are exercised directly in the new tests rather than mocked, giving genuine coverage. Both the happy path (modern id-keyed rows) and the legacy name-keyed fallback are tested, and the comma-quoting assertion validates the RFC-4180 escaping end-to-end. No unrelated code was modified.

No files require special attention.

Important Files Changed

Filename Overview
apps/sim/lib/copilot/tools/handlers/function-execute.ts Replaces the broken union-key CSV builder with a display-name-header / column-id-value approach, reusing shared export-format serializers and adding CSV-formula-injection safety.
apps/sim/lib/copilot/tools/handlers/function-execute.test.ts New test file covering both modern id-keyed rows and legacy name-keyed rows; all mocks are in place and the assertions match the expected CSV output accurately.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[inputTables ref] --> B[resolveTableRef]
    B --> C[queryRows]
    C --> D[rows.rows loop]
    D --> E[Header: column.name via neutralizeCsvFormula + toCsvRow]
    D --> F[Data row: getColumnId looks up col_id or name fallback]
    F --> G[row.data keyed by stable column id]
    G --> H[formatCsvValue applies formula-neutralize and type serialization]
    H --> I[toCsvRow applies RFC-4180 quoting and escaping]
    E --> J[csvLines joined with newline]
    I --> J
    J --> K[SandboxFile mounted at /home/user/tables/tbl_id.csv]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[inputTables ref] --> B[resolveTableRef]
    B --> C[queryRows]
    C --> D[rows.rows loop]
    D --> E[Header: column.name via neutralizeCsvFormula + toCsvRow]
    D --> F[Data row: getColumnId looks up col_id or name fallback]
    F --> G[row.data keyed by stable column id]
    G --> H[formatCsvValue applies formula-neutralize and type serialization]
    H --> I[toCsvRow applies RFC-4180 quoting and escaping]
    E --> J[csvLines joined with newline]
    I --> J
    J --> K[SandboxFile mounted at /home/user/tables/tbl_id.csv]
Loading

Reviews (2): Last reviewed commit: "fix(copilot): mount input tables with di..." | Re-trigger Greptile

…le-column-names

# Conflicts:
#	apps/sim/lib/copilot/tools/handlers/function-execute.test.ts
@TheodoreSpeaks TheodoreSpeaks merged commit 08bcacd into staging Jun 17, 2026
16 checks passed
@TheodoreSpeaks TheodoreSpeaks deleted the fix/code-mount-table-column-names branch June 17, 2026 21:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant