Skip to content

Anti-Patterns to Avoid

These patterns cause data loss, poor performance, or user frustration. Avoid them.

Redundant Formatting Anti-Pattern

The Problem

Applying the same formatting to the same range more than once in a workflow:

WRONG: Applying bold repeatedly

range_format(action: 'format-range', rangeAddress: 'A1:D1', bold: true)
// ... other operations ...
range_format(action: 'format-range', rangeAddress: 'A1:D1', bold: true, fillColor: '#4472C4')
// Bold was already applied - the second call re-applies it unnecessarily

Also wrong: calling format-range separately for each property instead of combining:

WRONG: Separate calls for each property

range_format(action: 'format-range', rangeAddress: 'A1:D1', bold: true)
range_format(action: 'format-range', rangeAddress: 'A1:D1', fillColor: '#4472C4')
range_format(action: 'format-range', rangeAddress: 'A1:D1', fontColor: '#FFFFFF')
range_format(action: 'format-range', rangeAddress: 'A1:D1', horizontalAlignment: 'center')

The Solution

Apply all formatting properties for a range in one format-range call:

CORRECT: One call per range

range_format(action: 'format-range', rangeAddress: 'A1:D1',
    bold: true, fillColor: '#4472C4', fontColor: '#FFFFFF', horizontalAlignment: 'center')

Apply each formatting operation once. If a subsequent step explicitly changes a property (e.g., "now make the title red"), apply it again — otherwise don't.

If the same formatting applies to multiple disjoint ranges, do not repeat format-range for each target:

WRONG: Repeating the same shared formatting payload

range_format(action: 'format-range', rangeAddress: 'A1:D1', bold: true, fillColor: '#4472C4', fontColor: '#FFFFFF')
range_format(action: 'format-range', rangeAddress: 'A12:D12', bold: true, fillColor: '#4472C4', fontColor: '#FFFFFF')
range_format(action: 'format-range', rangeAddress: 'A24:D24', bold: true, fillColor: '#4472C4', fontColor: '#FFFFFF')
CORRECT: One shared multi-range formatting call

range_format(action: 'format-ranges',
    rangeAddresses: ['A1:D1', 'A12:D12', 'A24:D24'],
    bold: true, fillColor: '#4472C4', fontColor: '#FFFFFF')

When Multiple Calls ARE Appropriate

  • Applying different formatting to different ranges
  • A later step explicitly overrides a previously set property
  • Applying a style (set-style) on top of individual properties (different actions)

Wrong Style System Anti-Pattern

The Problem

Applying range_format to cells that belong to an object with its own style system:

WRONG: Formatting a table header row with range_format

table(action: 'create', tableName: 'Sales', rangeAddress: 'A1:D10')
range_format(action: 'format-range', rangeAddress: 'A1:D1', bold: true, fillColor: '#4472C4')
// The table style already controls header appearance — this creates an inconsistent override
WRONG: Formatting PivotTable cells

pivottable(action: 'create-from-table', ...)
range_format(action: 'format-range', rangeAddress: 'B3:B20', fillColor: '#E2EFDA')
// Formatting is wiped on the next pivottable(refresh)

The Solution

Use the style system that belongs to each object type:

CORRECT: Table visual styling — one call at creation or via set-style

table(action: 'create', tableName: 'Sales', rangeAddress: 'A1:D10',
    tableStyle: 'TableStyleMedium2')
Object Correct style approach Do NOT use
Excel Tables table(action:'set-style') or tableStyle on create range_format on header/data rows
PivotTables Not supported — leave default range_format (wiped on refresh)
Charts chart_config(action:'set-style', styleNumber: 1-48) range_format
Plain cells/ranges range_format

Delete-and-Rebuild Anti-Pattern

The Problem

Deleting entire structures to make small changes:

WRONG: User wants to update cell B5

table(action: 'delete', tableName: 'SalesData')
range(action: 'set-values', values: [[entire dataset with B5 fixed]])
table(action: 'create', tableName: 'SalesData', ...)

This destroys: - Cell formatting - Conditional formatting rules - Data validation - Named ranges pointing to the table - PivotTable connections - DAX measures referencing the table

The Solution

Use targeted modifications:

CORRECT: Update only the changed cell

range(action: 'set-values', rangeAddress: 'B5', values: [[newValue]])

When Rebuild IS Appropriate

  • Fundamentally restructuring data (different columns)
  • Converting between table types
  • User explicitly requests replacement

Discovery Loop Anti-Pattern

The Problem

Repeating file(list), worksheet(list), or table(list) multiple times without taking action:

WRONG: Looping on discovery after an error

worksheet(action: 'list')           → gets sheet list
worksheet(action: 'list')           → gets same sheet list again
file(action: 'list')                → gets session list
worksheet(action: 'list')           → gets same sheet list again
... (dozens of repetitions)

This burns tokens, costs money, and never completes the task.

The Solution

If you already have a sessionId, use it. Do not rediscover:

CORRECT: Use the sessionId you already have

Error: "session expired"
→ file(action: 'open', path: original_path)  ← Re-open once, get new sessionId
→ Continue with the new sessionId immediately

The Rule

  • Max 2 retries for any session or file operation
  • After 2 failures: stop retrying, report the error, end your response
  • Never call list, worksheet(list), or table(list) more than twice in a row without doing something with the result

Confirmation Loop Anti-Pattern

The Problem

Asking for confirmation on every operation:

WRONG:

User: "Create a sales report"
AI: "Would you like me to create a new Excel file for the sales report?"
User: "Yes"
AI: "What would you like to name the file?"
User: "sales_report.xlsx"
AI: "Should I create it in your Documents folder?"
User: "Yes"
AI: "The file has been created. Would you like me to add headers?"
... (10 more questions)

The Solution

Execute with reasonable defaults, report results:

CORRECT:

User: "Create a sales report"
AI: "Created sales report at C:\Users\You\Documents\sales_report.xlsx with the following structure:
- Sheet 'Summary' with headers: Date, Product, Region, Sales
- Ready for data entry

What data would you like to add?"

When to Ask

  • Genuinely ambiguous requests
  • Destructive operations on existing data
  • User explicitly asked for options

Wrong Cell Update Anti-Pattern

The Problem

Reading entire range, modifying in memory, writing entire range back:

WRONG: Update one cell by rewriting thousands

data = range(action: 'get-values', rangeAddress: 'A1:Z1000')
data[4][1] = "new value"  // Modify row 5, column B
range(action: 'set-values', rangeAddress: 'A1', values: data)

This: - Transfers megabytes unnecessarily - Risks data corruption if interrupted - Destroys formulas (values only, not formulas) - Loses cell formatting

The Solution

Write only the changed cells:

CORRECT: Direct cell update

range(action: 'set-values', rangeAddress: 'B5', values: [["new value"]])

Session Leak Anti-Pattern

The Problem

Opening files without closing them:

WRONG: Session accumulation

file(action: 'open', filePath: 'file1.xlsx')  // Session 1
file(action: 'open', filePath: 'file2.xlsx')  // Session 2
file(action: 'open', filePath: 'file3.xlsx')  // Session 3
// ... never closed

Results: - Excel processes accumulate - Memory usage grows - File locks prevent other access - System becomes unresponsive

The Solution

Always close sessions:

CORRECT: Proper lifecycle

session1 = file(action: 'open', path: 'file1.xlsx')
// ... work with file1 ...
file(action: 'close', sessionId: session1, save: true)

session2 = file(action: 'open', path: 'file2.xlsx')
// ... work with file2 ...
file(action: 'close', sessionId: session2, save: true)

Ignoring Error Context Anti-Pattern

The Problem

Retrying failed operations without reading the error:

WRONG: Blind retry

datamodel(action: 'create-measure', ...) → Error: Table not in Data Model
datamodel(action: 'create-measure', ...) → Error: Table not in Data Model
datamodel(action: 'create-measure', ...) → Error: Table not in Data Model

The Solution

Read and act on error context:

CORRECT: Error-driven correction

datamodel(action: 'create-measure', ...) 
→ Error: Table 'Sales' not in Data Model
→ Suggested: table(action: 'add-to-data-model', tableName: 'Sales')

table(action: 'add-to-data-model', tableName: 'Sales')  // Fix prerequisite
datamodel(action: 'create-measure', ...)  // Now succeeds

Number Format Locale Anti-Pattern

The Problem

Using locale-specific format codes:

WRONG: German/European format

range(action: 'set-number-format', formatCode: '#.##0,00')  // German
range(action: 'set-number-format', formatCode: '# ##0,00')  // French

The Solution

Always use US format codes (Excel translates automatically):

CORRECT: US format codes (universal)

range(action: 'set-number-format', formatCode: '#,##0.00')

Excel displays the result in the user's locale setting, but the API requires US format input.

Load Destination Mismatch Anti-Pattern

The Problem

Wrong load destination for the workflow:

WRONG: Loading to worksheet when DAX is needed

powerquery(action: 'create', loadDestination: 'worksheet', ...)
datamodel(action: 'create-measure', ...)  // FAILS: table not in Data Model

The Solution

Match load destination to workflow:

CORRECT: Load to Data Model for DAX workflows

powerquery(action: 'create', loadDestination: 'data-model', ...)
powerquery(action: 'refresh', ...)
datamodel(action: 'create-measure', ...)  // Works
Workflow Goal Load Destination
View data in cells worksheet
Use in DAX/PivotTables data-model
Both viewing and DAX both
Intermediate staging connection-only

Skipping Power Query Evaluate Anti-Pattern

The Problem

Creating or updating Power Query queries without testing M code first:

WRONG: Creating permanent query with untested M code

powerquery(action: 'create', mCode: '...', ...)
// M code has syntax error → COM exception with cryptic message
// Now workbook is polluted with broken query

This causes: - Broken queries persisted in workbook - Cryptic COM exceptions instead of helpful M error messages - Need manual Excel cleanup to remove broken queries - Wasted time debugging in wrong layer

The Solution

Always evaluate M code BEFORE creating permanent queries:

CORRECT: Test-first development workflow

// Step 1: Test M code without persisting
powerquery(action: 'evaluate', mCode: '...')
// → Returns actual data preview with columns and rows
// → Better error messages if M code has issues

// Step 2: Create permanent query with validated code
powerquery(action: 'create', mCode: '...', ...)

// Step 3: Load data to destination
powerquery(action: 'refresh', ...)

Benefits: - Catch syntax errors and missing sources BEFORE persisting - See actual data preview (columns, sample rows) - Better error messages than COM exceptions - No cleanup needed - temporary objects auto-deleted - Like a REPL for M code

When Evaluate IS Optional

  • Trivial literal tables: #table({"Column1"}, {{123}})
  • M code already validated in previous evaluate call
  • Copying known-working query from another workbook

When to Retry With Evaluate

If create/update fails with COM error, use evaluate to get detailed Power Query error message:

powerquery(action: 'create', ...)  // → COM exception
powerquery(action: 'evaluate', mCode: '...')  // → Detailed M error
// Fix M code based on error
powerquery(action: 'create', ...)  // → Success