Carta MCP から Excel 予算ファイルに実績データを書き込むスキルです。予算/実績/差異(予算と実績の乖離)の列を追加・組み込んだり、タグビュー(タグで分類した表示)タブやベンダービュー(仕入先ごとの表示)タブを追加したりできます。 **使用場面:** - [企業/運用会社]の実績データを取得・追加して既存ファイルに反映させたい - 予算/実績/差異の列を組み込みたい - 部門・コストセンター・タグ・仕入先ごとの実績を表示したい - 翌月/期間の列を追加したい - 予算を[月]まで延長したい - 仕入先別の内訳や仕入先ごとの支出推移を見たい **このスキルでできないこと:** - ペース分析(進捗状況) - 「調子はどう?」といった定性的な質問 - 「[期間]の予算対実績」や差異分析クエリ(carta-budget-analysis スキルを使用) - 新しい予算の作成(carta-create-budget スキルを使用) - シナリオ分析 - 損益計算書やバランスシートの統合 - Carta から保存済みの運用会社予算を取得すること(carta-fetch-budget スキルを使用) このスキルは既存ファイルに実績を追加するもので、予算ファイル自体を取得するものではありません。
Write actuals into an existing Excel budget workbook from Carta MCP — add/interleave Budget/Actual/Variance columns, a tag-view tab, or a vendor-view tab. TRIGGER: pull/fetch/get/retrieve/refresh/sync/add actuals for [firm/ManCo], interleave Budget/Actual/Variance, actuals by department/cost center/tag/vendor, add next month/period column, extend budget through [month], broken down by vendor, vendor summary/spend over time/vendors across [period]. NOT: pacing, "how are we doing", "budget versus actual for [period]", or variance-analysis queries (carta-budget-analysis); new budgets (carta-create-budget); scenarios; consolidating P&L / balance sheet. NOT: pulling a stored ManCo budget from Carta (carta-fetch-budget) — this skill writes actuals into an existing workbook, not the budget itself.
<!-- carta:instrumentation-fallback -->
<IMPORTANT>
_instrumentation_v2 is anonymous Carta skill-usage telemetry. ALWAYS include it in your Carta MCP tool-call arguments — the plugins' hooks overwrite it when they run, so always sending it is safe. Shape:
_instrumentation_v2: {"skills": ["<plugin>:<skill>", ...], "model": "<model-id>"}
skills is cumulative for the session: list every Carta skill loaded so far, not only the one driving this call. Namespace each entry "plugin:skill" and keep them oldest-first, with the most recently used skill last. List only Carta skills (carta-cap-table:*, carta-crm:*, carta-investors:*) — never skills from non-Carta plugins.
</IMPORTANT>
[PATTERN carta-writing-style v0.0.2] [PATTERN etiquette v0.0.6] [PATTERN text v0.0.8] [PATTERN tables v0.0.12] [PATTERN carta-watermark v0.0.10] [PATTERN menus-and-flows v0.0.7] [PATTERN base v0.1.0]
Entry point for updating actuals in an existing budget. Seven references:
references/add-actuals-columns.md — Layout A: interleave Budget / Actual / Variance per month on the Budget tab (recommended for active tracking).references/add-actuals-tab.md — Layout B: add a peer <year> Actuals tab alongside the Budget tab.references/refresh-existing.md — Layout C: overwrite stale actuals cells in columns that already exist.references/add-period.md — Layout D: append the single next month/quarter column.references/tag-view.md — Layout E: new tab with actuals sliced by reporting dimension (department, project code, class, etc.) and a three-row period / category / tag header. Only offered when the entity has tagged journal data.references/vendor-view.md — Layout F: new tab with actuals sliced by vendor, with per-vendor subtotals and a 'No vendor' section. Only offered when the entity has vendor-tagged journal data.references/inline-vendor.md — Layout G: vendor sub-rows added inline to the current actuals tab; accounts become subtotals of their vendor children.references/vendor-only-view.md — Layout H: new tab with one row per vendor across a timeline — no GL account sub-rows. The lightweight vendor summary view.references/get-actuals.md — internal helper, the canonical actuals-query routine.Audience is an accountant in Excel. Plain English only. Never surface MCP
identifiers, DWH column names, UUIDs, raw JSON, SQL, or gate labels.
Currency: $X,XXX positive, ($X,XXX) negative, totals bolded.
Differences are absolute. Status: ✅ Match | ⚠ Mismatch ($X diff) | ❌ Missing in Carta | ❌ Missing in Client Doc.
Closing summary link is a workbook citation (<citation:Sheet!Range>) in
Claude for Excel mode, and a file:// path in Claude Code / Cowork mode.
Every numbered choice in this skill — including the closing
next-step menu — MUST be presented via AskUserQuestion. Never
render options as a bare code-fenced markdown list. The
AskUserQuestion tool is in allowed-tools; use it. Bare-text menus
break the chooser UI in Claude for Excel and force the user to type
the number.
carta-create-budget.carta-fetch-budget.carta-budget-analysis.carta-budget-scenarios.carta-consolidating-pnl.carta-consolidating-balance-sheet.Execute all gates silently. Do not narrate tool calls, intermediate results, or status updates. Only speak at explicit decision points: Gate 0.5 (if runtime is ambiguous), Gate 1 (destination chooser), Gate 2 (layout choice — always ask), Gate 3 (period/parameter gate), Gate 5.5 (memo-based vendor inference — offer only, Layouts F/G/H with a non-empty 'No vendor' bucket), Gate 6 (approval), and Gate 8 (next-step menu).
Mandatory telemetry beacon — run this before any skip-gate check: If <SERVER> and <ENTITY_UUID> are already in context, call mcp__<SERVER>__set_context(firm_id=<ENTITY_UUID>, _instrumentation={"plugin": "carta-investors", "skills": ["carta-fetch-actuals"]}) as your very first action. Do not skip this even when all data is already available — it records this skill invocation. If either is unknown, it fires in Gate 0 below.
Before Gate 0, check whether these context variables are already set from an earlier budgeting skill call in the same session:
<SERVER> — connected Carta MCP server prefix<ENTITY_NAME> and <ENTITY_UUID> — the resolved entity<RUNTIME> — excel-addin or local-fileIf all four are in context: skip Gates 0 and 0.5 entirely. Call mcp__<SERVER>__set_context(firm_id=<ENTITY_UUID>, _instrumentation={"plugin": "carta-investors", "skills": ["carta-fetch-actuals"]}) to re-anchor the session scope and record this skill invocation. In Gate 3, pre-fill <ENTITY_NAME> and skip asking for it — ask only for the period. Proceed from Gate 1 (destination), then Gate 2 (layout choice), then Gate 3 (period).
If any is missing (fresh session or cold invocation): run Gates 0 and 0.5 in order, then continue from Gate 1.
Do not ask "which firm?" or "which runtime?" when those are already established from the skill the user just ran.
Scan the tools available in the conversation for any matching mcp__*__welcome. Extract the server identifier — the middle segment between the first and last __. Examples: mcp__carta__welcome → carta, mcp__claude_ai_Carta__welcome → claude_ai_Carta.
If none found: tell the user no Carta MCP is connected and stop.
If exactly one found: call mcp__<SERVER>__welcome(_instrumentation={"plugin": "carta-investors", "skills": ["carta-fetch-actuals"]}) to verify. This is <SERVER>.
If multiple found: ask the user which to use via AskUserQuestion. Default to carta (production) if present.
Don't call any other mcp__<SERVER>__* tool before welcome — every other command is gated and will return a reminder.
Resolve firm: if user named one → mcp__<SERVER>__list_contexts(firm_name="<entity>", _instrumentation={"plugin": "carta-investors", "skills": ["carta-fetch-actuals"]}) → disambiguate via AskUserQuestion if multiple → mcp__<SERVER>__set_context(firm_id=<FIRM_UUID>, _instrumentation={"plugin": "carta-investors", "skills": ["carta-fetch-actuals"]}). Do not use call_tool for list_contexts or set_context — call the granular tools directly with _instrumentation as shown.
DWH param-name traps: dwh:execute:query takes sql: not query:. dwh:get:table_schema takes table_name: not table:. format accepts "ndjson" / "markdown", not "csv".
If no firm was named, defer to Gate 3.
Detect whether this is Claude for Excel (workbook is open in the
add-in) or Claude Code / Cowork (working with a .xlsx file on
disk). See carta-create-budget/SKILL.md Gate 0.5 for the heuristic — same
rule applies here.
If unclear, ask the user via AskUserQuestion:
"How are you working with the budget — inside Excel via Claude for Excel, or as a local .xlsx file (Claude Code / Cowork)?"
Store <RUNTIME> (excel-addin or local-file) for Gates 1, 4, 7, 8.
Branches by <RUNTIME>.
If <RUNTIME> is excel-addin:
Empty-workbook shortcut: if the active workbook has one sheet, maxRows == 0, no other tabs (typically a fresh Book1.xlsx/Sheet1), skip the chooser. Announce the rename in one sentence — "I'll use the empty workbook you have open and rename Sheet1 to <TARGET_TAB>." — then proceed. The chooser only exists to protect non-empty state; an empty workbook has none. The chooser still applies whenever there is data or more than one tab.
Where should I write the updates?
If user picks "update directly", confirm which tab explicitly. If multiple tabs look like budgets, ask which one.
If <RUNTIME> is local-file:
Where is the budget file, and where should the updated version land?
-updated suffix by default.If the user gave a path in the original prompt, skip the choice. Store
<DESTINATION> (open workbook + tab in add-in mode, or .xlsx path +
sheet name in local-file mode).
Four layouts are valid for putting actuals into a workbook, and the same prompt can plausibly mean any of them. Always ask the user how the actuals should appear — never assume from the prompt's phrasing alone.
Use AskUserQuestion:
How should the actuals appear in the workbook?
| # | Option | Reference loaded |
|---|---|---|
| 1 | Interleave Budget / Actual / Variance columns per month on the Budget tab ← recommended | add-actuals-columns.md |
| 2 | Add a separate <year> Actuals tab alongside the Budget tab |
add-actuals-tab.md |
| 3 | Refresh existing Budget / Actual / Variance cells (the cells are there, just stale) | refresh-existing.md |
| 4 | Add only the next single period column | add-period.md |
| 5 | Build a tag-view tab — actuals sliced by reporting dimension (department, project code, class, etc.) | tag-view.md — only offered when the entity has tagged data; see Gate 2.5 |
| 6 | Build a vendor-view tab — actuals sliced by vendor, with GL account detail (per-vendor subtotals and collapsible account rows) | vendor-view.md — only offered when the entity has vendor-tagged data; see Gate 2.6 |
| 7 | Add vendor rows inline to the current actuals tab — insert vendor sub-rows under each account; accounts become subtotals of their vendor children | inline-vendor.md — only offered when the active tab is already an actuals tab; see Gate 2.7 |
| 8 | Build a vendor summary tab — one row per vendor across a timeline, no GL detail | vendor-only-view.md — only offered when the entity has vendor-tagged data; see Gate 2.8 |
Use the user's prompt only as a hint for which option to highlight — never as authority to skip the question:
| Phrase in the prompt | Hint |
|---|---|
"interleave", "Budget / Actual / Variance", "variance by month", "add <year> actuals" (no other clue) |
Option 1 (also the default ← recommended) |
| "add a tab", "track on its own tab", "separate actuals tab" | Option 2 |
| "refresh", "the actuals are stale", "pull latest", "sync" | Option 3 |
"add next month", "extend through <month>", "next period" |
Option 4 |
| "by department", "by tag", "by cost center", "split by", "broken down by", "by reporting tag", "by project code" | Option 5 |
| "by vendor", "broken down by vendor", "vendor view", "which vendors", "vendor breakdown" — and the active tab is already an actuals tab | Offer Options 6 and 7 together; 7 ← recommended |
| "by vendor", "broken down by vendor", "vendor view", "which vendors", "vendor breakdown" — no actuals tab open | Option 6 |
| "break by vendor on this tab", "add vendors here", "vendor breakdown on this tab", "enrich this tab with vendors" | Option 7 |
| "vendor summary", "vendor spend over time", "vendors across", "which vendors did we pay", "vendor totals", "vendor breakdown without accounts", "just vendors" | Option 8 |
Option 5 availability: always show Layout E in the chooser. If the user picks it and Gate 2.5 finds no tag data on the entity, tell them in one sentence and fall back to Layout A automatically. Do not pre-filter the chooser — the entity name needed for the probe is not available until Gate 3.
Option 6 availability: always show Layout F in the chooser. If the user picks it and Gate 2.6 finds no vendor data on the entity, tell them in one sentence and fall back to Layout A automatically. Do not pre-filter the chooser.
Option 7 availability: only show Layout G when the active sheet is already an actuals tab (its name contains "Actuals" or the user is explicitly on it). If the user picks it and Gate 2.7 finds no vendor data, tell them in one sentence and fall back to Option 6 (new vendor-view tab) instead.
Option 8 availability: always show Layout H in the chooser. If the user picks it and Gate 2.8 finds no vendor data on the entity, tell them in one sentence and fall back to Layout A automatically. Do not pre-filter the chooser.
The user's pick locks the reference to load for the rest of the
workflow. Immediately call read_skill for the chosen layout — do not reconstruct from memory:
| Layout chosen | Call |
|---|---|
| Option 1 — Interleave columns | read_skill(file_path="references/add-actuals-columns.md") |
| Option 2 — Add actuals tab | read_skill(file_path="references/add-actuals-tab.md") |
| Option 3 — Refresh existing | read_skill(file_path="references/refresh-existing.md") |
| Option 4 — Add single period | read_skill(file_path="references/add-period.md") |
| Option 5 — Tag-view tab | read_skill(file_path="references/tag-view.md") |
| Option 6 — Vendor view tab | read_skill(file_path="references/vendor-view.md") |
| Option 7 — Vendor inline | read_skill(file_path="references/inline-vendor.md") |
| Option 8 — Vendor summary tab | read_skill(file_path="references/vendor-only-view.md") |
Why we always ask: the same prompt — "add 2026 actuals by month" — can mean Option 1, 2, or 3 depending on the user's intent and the current state of their workbook. Guessing wrong and rebuilding costs the user a corrective prompt. Asking once costs one click. Choose the click.
Skip this gate entirely unless the user chose Layout E at Gate 2.
Silent probe — no user-facing output. Layout E shows all firm tag categories side by side under each period band — no "which dimension?" picker. The probe's job is to discover the firm's tag taxonomy from REPORTING_TAGS_JSON (or fall back to the flat REPORTING_TAGS column when only that's populated).
call_tool({"name": "dwh__execute__query", "arguments": {
"sql": "SELECT
COUNT_IF(REPORTING_TAGS_JSON IS NOT NULL) AS json_rows,
COUNT_IF(REPORTING_TAGS IS NOT NULL) AS flat_rows
FROM <journal_entries_table>
WHERE FUND_NAME = '<entity_name>'
AND EFFECTIVE_DATE >= DATEADD('year', -1, CURRENT_DATE)",
"format": "markdown",
"_instrumentation": {"plugin": "carta-investors", "skills": ["carta-fetch-actuals"]}
}})
json_rows > 0 → JSON path. Skip Probe 2 — go directly to Probe 3 (JSON path). Probe 3 returns both category names and cardinality in one query, making a separate category-discovery query redundant.json_rows == 0 AND flat_rows > 0 → flat path. Set <CATEGORIES> = ["Reporting Tag"] and continue to Probe 3 (cardinality).Run one query that returns the value count per category (used to drive the wide vs long decision):
JSON path:
call_tool({"name": "dwh__execute__query", "arguments": {
"sql": "SELECT f.key::TEXT AS category, COUNT(DISTINCT f.value::TEXT) AS n_values
FROM <journal_entries_table>,
LATERAL FLATTEN(input => REPORTING_TAGS_JSON) f
WHERE FUND_NAME = '<entity_name>'
AND REPORTING_TAGS_JSON IS NOT NULL
AND EFFECTIVE_DATE >= DATEADD('year', -1, CURRENT_DATE)
GROUP BY 1
ORDER BY 1",
"format": "markdown",
"_instrumentation": {"plugin": "carta-investors", "skills": ["carta-fetch-actuals"]}
}})
Flat path:
call_tool({"name": "dwh__execute__query", "arguments": {
"sql": "SELECT 'Reporting Tag' AS category, COUNT(DISTINCT REPORTING_TAGS) AS n_values
FROM <journal_entries_table>
WHERE FUND_NAME = '<entity_name>'
AND REPORTING_TAGS IS NOT NULL
AND EFFECTIVE_DATE >= DATEADD('year', -1, CURRENT_DATE)",
"format": "markdown",
"_instrumentation": {"plugin": "carta-investors", "skills": ["carta-fetch-actuals"]}
}})
Store <CATEGORIES> as the sorted list of distinct category values returned, and store <CARDINALITY> as the map of category → n_values. (Probe 3 returns both in one pass — no separate Probe 2 needed.) Compute the total column count:
total_columns = sum(n_values for each category) + len(<CATEGORIES>)
The + len(<CATEGORIES>) term covers the per-category Total columns. If the run uses Quarter or Month aggregation across multiple period blocks, multiply by the number of period blocks for the layout decision.
The authoritative thresholds live in references/tag-view.md §"Cardinality guard" — ≤ 24 wide / 25–36 ask / > 36 long. Keep that file as the single source of truth; the inline cutoffs below mirror it for runtime convenience and must move together if the table changes.
If total_columns > 24 (single period) or total_columns × n_period_blocks > 24 (multi-period), ask via AskUserQuestion:
The tag-view tab would have
<N>columns across<C>categories. With that many, should I build a wide table (one column per tag per category per period) or a long table (one row per tag per account)?
Store <TAG_LAYOUT> (wide | long). Default wide for ≤ 24 (no question asked); default long for > 36 (no question asked).
Skip this gate entirely unless the user chose Layout F at Gate 2.
Silent probe — no user-facing output. Checks whether VENDOR_NAME is populated and counts distinct vendors.
call_tool({"name": "dwh__execute__query", "arguments": {
"sql": "SELECT
COUNT_IF(VENDOR_NAME IS NOT NULL) AS tagged_rows,
COUNT_IF(VENDOR_NAME IS NULL) AS untagged_rows,
COUNT(DISTINCT VENDOR_NAME) AS distinct_vendors
FROM <journal_entries_table>
WHERE FUND_NAME = '<entity_name>'
AND ACCOUNT_TYPE >= '4000'
AND EFFECTIVE_DATE >= DATEADD('year', -1, CURRENT_DATE)",
"format": "markdown",
"_instrumentation": {"plugin": "carta-investors", "skills": ["carta-fetch-actuals"]}
}})
tagged_rows > 0 → vendor data exists. Store <VENDOR_COUNT> = distinct_vendors and <HAS_UNTAGGED> = (untagged_rows > 0). Continue to Gate 3.tagged_rows == 0 → no vendor data on this entity. Tell the user in one plain-English sentence — "Your journal data doesn't have any vendor information, so I'll build a flat actuals view instead." — and fall back to Layout A.Skip this gate entirely unless the user chose Layout G at Gate 2.
Silent probe — no user-facing output. Same probe as Gate 2.6 — confirms vendor data exists before rebuilding the tab inline.
Skip this gate entirely unless the user chose Layout H at Gate 2.
Silent probe — no user-facing output. Same probe as Gate 2.6 — checks whether
VENDOR_NAME is populated and counts distinct vendors.
call_tool({"name": "dwh__execute__query", "arguments": {
"sql": "SELECT
COUNT_IF(VENDOR_NAME IS NOT NULL) AS tagged_rows,
COUNT_IF(VENDOR_NAME IS NULL) AS untagged_rows,
COUNT(DISTINCT VENDOR_NAME) AS distinct_vendors
FROM <journal_entries_table>
WHERE FUND_NAME = '<entity_name>'
AND ACCOUNT_TYPE >= '4000'
AND EFFECTIVE_DATE >= DATEADD('year', -1, CURRENT_DATE)",
"format": "markdown",
"_instrumentation": {"plugin": "carta-investors", "skills": ["carta-fetch-actuals"]}
}})
tagged_rows > 0 → vendor data exists. Store <VENDOR_COUNT> = distinct_vendors
and <HAS_UNTAGGED> = (untagged_rows > 0). Continue to Gate 3.tagged_rows == 0 → no vendor data on this entity. Tell the user in one plain-English
sentence — "Your journal data doesn't have any vendor information, so I'll build a
flat actuals view instead." — and fall back to Layout A.In one AskUserQuestion, confirm every parameter the prompt didn't already specify.
Entity: confirm <ENTITY_NAME> — the exact FUND_NAME value used in DWH queries. If the user named one at Gate 0, pre-fill it and only ask if it's ambiguous.
Period: offer smart defaults based on today's date (currently May 2026):
What period should I pull actuals for?
| # | Label | Date range |
|---|---|---|
| 1 ← recommended | Full year 2026 | Jan 1 – Dec 31, 2026 |
| 2 | YTD 2026 (Jan – May) | Jan 1 – May 31, 2026 |
| 3 | Q2 2026 (Apr – Jun, in progress) | Apr 1 – Jun 30, 2026 |
| 4 | Full year 2025 | Jan 1 – Dec 31, 2025 |
| 5 | Custom range — I'll specify start / end month | — |
Adapt ← recommended and visible options to context: YTD if the user said
"latest"; full year if they said "2026 actuals"; prior year if they said "2025".
Always compute the current quarter's label dynamically from today's date — do
not hardcode Q2.
If the prompt already specified a period (e.g. "Q1 2026", "full year 2025"), store it directly and skip the period question.
Store <PERIOD_START> (first day, YYYY-MM-DD) and <PERIOD_END> (last day).
Match strategy (Layouts A–D only): name first then GL code (default) vs GL code only. Omit for Layouts E, F, G, or H (no existing-sheet matching needed — these layouts always write a new tab).
Store <ENTITY_NAME>, <PERIOD_START>, <PERIOD_END>, <MATCH_STRATEGY> (Layouts A–D).
Skip this sub-gate for Layouts A–D and G — aggregation is always monthly there. Set <AGGREGATION> = MONTH and continue.
For Layouts E, F, and H, this MUST be a separate AskUserQuestion call — do not bundle with the period question above, and do not infer the answer from the period range. A YTD period (e.g. Jan–May) does not mean "Quarter"; a full-year period does not mean "Year". The user picks the period independently from the aggregation.
Aggregate columns by:
| # | Label |
|---|---|
| 1 ← recommended | Year — one period block per year |
| 2 | Quarter — one block per quarter |
| 3 | Month — one block per month |
Store <AGGREGATION> (YEAR | QUARTER | MONTH).
Hard rule: if you find yourself building a tag-view or vendor-view with multiple period blocks (e.g. Q1+Q2) without an AskUserQuestion whose answer literally selected one of the three options above, you skipped this gate. Stop and ask before proceeding to Gate 4.
Skip this sub-gate for Layouts A–E and H, or when <RUNTIME> is local-file (row outline grouping is not supported in the local-file path). Layout H has no GL sub-rows to collapse.
Ask via AskUserQuestion — a separate call from Gate 3a above:
Should vendor detail rows be collapsible in Excel?
| # | Label | Description |
|---|---|---|
| 1 ← recommended | Yes — collapsed by default | Rows hidden on open; click + to expand a vendor section |
| 2 | Yes — expanded by default | Rows visible; click − to collapse a vendor section |
| 3 | No grouping | Flat tab, no outline controls |
Store <VENDOR_GROUPING> (collapsed | expanded | none).
If <RUNTIME> is excel-addin:
Use the Excel add-in's runtime read tools to inspect the budget tab — header row, line-item rows, actuals/budget columns, formula rows.
If <RUNTIME> is local-file:
uv run "${CLAUDE_PLUGIN_ROOT}/scripts/read_workbook.py" \
"<DESTINATION_PATH>" --sheet "<BUDGET_SHEET>"
Parse the resulting JSON to identify the same structure (headers,
line items, formula rows). Treat any cell where is_formula: true as
load-bearing — never overwrite it.
Layout E: use the category-grouped query from
references/tag-view.md §SQL. Pick the JSON path
when Gate 2.5 detected REPORTING_TAGS_JSON rows; the flat path when only
REPORTING_TAGS was populated. Substitute <period_trunc> from <AGGREGATION>,
<period_start> from <PERIOD_START>, <period_end> from <PERIOD_END>, and
<entity_name> from <ENTITY_NAME>. The JSON path discovers <CATEGORIES>
inside the query via LATERAL FLATTEN — do not parameterize the category list
into the SQL. All other rules below apply unchanged.
Layout F: use the vendor query from
references/vendor-view.md §SQL. Substitute <period_trunc>
from <AGGREGATION>, <period_start> from <PERIOD_START>, <period_end> from
<PERIOD_END>, and <entity_name> from <ENTITY_NAME>. The query uses
COALESCE(VENDOR_NAME, 'No vendor') so NULL-vendor entries roll into a single
'No vendor' section — do not run a second query for untagged rows. All other hard
rules (entity scoping, books date, sign convention, P&L scope) apply unchanged.
Layout H: use queries/actuals-by-vendor-period.sql directly. Substitute <period_trunc> from <AGGREGATION>, <period_start> from <PERIOD_START>, <period_end> from <PERIOD_END>, and <entity_name> from <ENTITY_NAME>. Same COALESCE(VENDOR_NAME, 'No vendor') convention; same hard rules as Layout F. The query returns (vendor_name, period, signed_amount) — no gl_code or account_name columns.
Layouts A–D: call references/get-actuals.md for the main actuals query. In parallel, call read_skill(file_path="references/vendor-actuals.md") and run the vendor actuals query — this loads <VENDOR_ACTUALS> into session context so vendor questions (e.g. "which vendor is driving Legal Fees?") are answerable for the rest of the session without a second round-trip. Never write inline SQL outside those files.
After the actuals are loaded (Layouts F, G, H only): if the built data structure has a non-empty No vendor bucket, go to Gate 5.5 before the pre-build review. For every other layout, and whenever the No vendor bucket is empty, skip Gate 5.5 entirely and proceed to Gate 6.
This gate is strictly opt-in and changes nothing by default. The No vendor section renders exactly as before unless the user explicitly asks for inference. Run this gate ONLY when all of these hold:
<LAYOUT> is F, G, or H (the layouts with a No vendor section), andNo vendor entry, andIf any condition is false, do nothing — skip to Gate 6.
AskUserQuestion)Compute the No vendor bucket's entry count and total from the already-loaded data. Then ask via AskUserQuestion — never a bare-text menu:
question: "<N> entries totalling <total> have no vendor. Want me to infer vendors from their memos?" (format <total> per the resolved currency)header: "No vendor"multiSelect: falseoptions:
description: "No inference. The 'No vendor' section stays exactly as pulled from the ledger.")description: "Read each memo and propose a likely vendor. You approve the list before anything is written.")If the user picks option 1 (or the default when they dismiss), do nothing — proceed to Gate 6 with the No vendor bucket unchanged. Only continue to Step 2 when the user picks option 2 (or asked for inference up front).
Run queries/no-vendor-memo-lines.sql. Resolve <memo_column> from the Gate 0 DWH schema lookup (candidates: MEMO, DESCRIPTION, LINE_MEMO, NARRATIVE). Substitute <entity_name>, <period_trunc> (from <AGGREGATION>), <period_start>, <period_end>.
If the table has no memo-like column, tell the user in one plain-English sentence — "These entries don't carry a memo I can read, so I'll leave them as 'No vendor'." — and proceed to Gate 6 unchanged. Do not retry with a different column guess more than once.
Build the candidate list of existing vendor names on this entity — the distinct named vendors already in the loaded data (<VENDOR_ACTUALS> for Layout G, or the named-vendor keys of the built pivot for F/H). For each memo:
"AMZN Mktp US*2Z4" → existing vendor Amazon). Prefer existing vendors so amounts reconcile to the vendor rows already on the sheet instead of spawning near-duplicate spellings."Stripe invoice #4471" with no existing Stripe row → new vendor Stripe).No vendor when you are not confident — do not force a match. A memo that is a bare reference number, an internal transfer note, or otherwise ambiguous stays untagged.Never write inferred vendor tags back to Carta — this is a report-only enrichment of the workbook. The underlying journal entries are not modified.
AskUserQuestion)Output a preview table as a normal message (not inside the AskUserQuestion), grouped so existing-vendor matches and new-vendor proposals are visually distinct:
| Memo | Inferred vendor | New or existing? | Amount |
|---|---|---|---|
| AMZN Mktp US*2Z4 | Amazon | Existing | 1,240 |
| Stripe invoice #4471 | Stripe | New | 600 |
Format every amount in this table per the fund's resolved currency (e.g. 1,240 EUR / 1,240 USD) — never hardcode a $ prefix.
Follow the table with one line: "<K> of <N> 'No vendor' entries matched (<total matched>). <N−K> stay as 'No vendor'." Then call AskUserQuestion:
question: "Apply these inferred vendors to the report?"header: "Confirm"multiSelect: falseoptions:
description: "Fold every matched amount into the vendor rows, flagged 'inferred from memo'.")description: "Skip the new-vendor proposals; only reassign amounts that matched a vendor already on the sheet.")description: "Discard the inferences and render the section as originally pulled.")Wait for an explicit choice. On option 3, proceed to Gate 6 with the No vendor bucket unchanged.
For each approved memo→vendor mapping, move its signed_amount (per period, and per GL account for Layouts F/G) out of the No vendor bucket and into the target vendor:
No vendor. If the bucket becomes empty after reassignment, drop the No vendor section.Mark every vendor row that received an inferred amount so Gate 7 can attach a cell comment (see each layout reference's "Inferred vendors" section) — the flag text is "inferred from memo". Store <INFERRED_VENDORS> = the list of (vendor, amount, sample_memo, is_new) mappings applied, for the Gate 6 preview and the Gate 8 summary.
The reassigned structure then flows into Gate 6 and Gate 7 exactly like any other vendor data — no separate write path.
Preview table grouped by:
If any rows carry the low-confidence — sparse history flag (account
has < 6 months of activity in the lookback window), surface the count
above the table.
If Gate 5.5 ran and <INFERRED_VENDORS> is non-empty, add an Inferred
vendors group — Vendor | Amount | New or existing | Sample memo — so the
reassignments are visible one last time before the write. Format every amount
per the fund's resolved currency (e.g. 1,240 EUR / 1,240 USD) — never
hardcode a $ prefix. State the residual that stayed in No vendor.
Output the preview tables above as a normal conversation message. Then call AskUserQuestion immediately after — the question field must be a single short sentence; never include preview content inside it.
question: "Approve applying these updates?"header: "Approval"multiSelect: falseoptions:
description: "Writes the actuals to the destination chosen in Gate 1.")The ← recommended marker goes inside the description field of option 1, not as a suffix on the label.
Wait for explicit OK before writing.
Hard rule: no workbook-write tool (Excel-add-in cell write, execute_office_js that mutates state, write_workbook.py, or any equivalent) runs before this gate's AskUserQuestion returns the user's explicit "Approve and write" choice. If you catch yourself about to call a workbook-write tool without that approval recorded, stop and run this gate first.
Before calling execute_office_js with state-mutating code, setValues, write_workbook.py, or any other workbook-write tool, look back at your tool history. Find the most recent AskUserQuestion you sent. Does its answer literally include "Approve and apply the updates"? If NO, Gate 6 did not pass — send the Gate 6 approval menu now and wait for the explicit answer.
Do not interpret upstream answers as approval. A Gate 2 layout response, a Gate 3 period-range answer, or any prior AskUserQuestion whose answer is not literally "Approve and apply the updates" does NOT clear this gate.
execute_office_js calls (excel-addin runtime)The most common failure mode is bundling cell writes + formatting + logo + verification into one writeSheet(...) function — the model writes the cells, returns, hardcodes the logo height, and the user gets a misaligned logo they have to resize manually. Do not combine the cell-write call with the brand block in a single office.js block.
execute_office_js. Return.execute_office_js. See the combined verification block below.Returning from Call 1 does NOT finish Gate 7. The final combined verification call must appear in your tool history before Gate 8 summary.
The single most common logo regression is hardcoding shape.height = 48 (or any other pixel value) instead of using the actual E1:E3 row-band height. Excel's row heights depend on font sizes set during Call 1, so the band height can vary tab-to-tab. The model that hardcodes 48 produces a logo that either spills past row 3 or sits inside row 1 — the user then has to resize manually. Paste this block verbatim per tab; substitute only <TAB_NAME>:
const base64 = blobs.getText("assets/powered_by_carta.b64.txt").trim();
const sheet = context.workbook.worksheets.getItem("<TAB_NAME>");
const shapes = sheet.shapes;
shapes.load("items/name");
await context.sync();
// De-dup: remove any prior CartaLogo so re-runs don't stack shapes.
for (const s of shapes.items) {
if (s.name === "CartaLogo") s.delete();
}
await context.sync();
// Anchor to the FULL row band E1:E3 — never a single cell.
const rows = sheet.getRange("E1:E3");
rows.load(["left", "top", "height"]);
await context.sync();
const image = sheet.shapes.addImage(base64);
image.name = "CartaLogo";
image.load(["width", "height"]);
await context.sync();
const ratio = image.width / image.height;
image.lockAspectRatio = false;
image.height = rows.height; // ← match actual row-band height, never a pixel literal
image.width = rows.height * ratio;
image.left = rows.left;
image.top = rows.top;
image.lockAspectRatio = true;
await context.sync();
Forbidden patterns (these reproduce the manual-resize bug):
image.height = 48 (or any number literal) — height MUST come from rows.height.sheet.getRange("E1") instead of sheet.getRange("E1:E3") — single-cell anchor loses the band height.CartaLogo shape on top of the first.image.lockAspectRatio = false before sizing — Excel resists width changes if locked.Only touch the cells the user approved. Do not edit formulas elsewhere in the sheet (subtotals are formula-driven and will auto-update).
Before any write, call both of these in the same message (parallel reads):
read_skill(file_path="references/branding-and-header.md") — 4-row metadata band, logo placement, blobs.getText asset pattern, cell-comment API.read_skill(file_path="references/<layout-from-gate-2>.md") — the layout file chosen in Gate 2 (e.g. add-actuals-columns.md for Layout A).Do not reconstruct either spec from memory. Both files must be in your context before generating any execute_office_js or write_workbook.py code. The branding-and-header.md file defines:
sheet.getRange("1:5").insert(...) in Excel add-in mode, or via prepended row writes in local-file mode).Read).If <RUNTIME> is excel-addin:
references/add-actuals-columns.md §5 ("Build the rebuild payload") and apply its header / column / format spec verbatim — especially the two-row header (row N = merged month labels, row N+1 = Budget / Actual / Variance sub-headers — spelled out in full, never abbreviated). Then use the add-in's cell-write tools to execute the payload.references/tag-view.md §"Writing the workbook (excel-addin runtime)" and follow it verbatim — 3-row period/category/tag header, range.merge(true) for period and category bands. Do NOT freeze panes — same rule as Layouts A–D and the rest of the Carta budgeting skills.references/vendor-view.md §"Writing the workbook (excel-addin runtime)" and follow it verbatim — 2-row period/vendor header, range.merge(true) for period bands, per-vendor subtotals, 'No vendor' section at the bottom. Do NOT freeze panes.references/inline-vendor.md §"Write sequence (excel-addin runtime)" and follow it verbatim — clear rows 7+, rebuild account rows (bold, SUM formula) + vendor sub-rows (indented four spaces, hardcoded amounts, locale-specific currency token), then group and collapse. Gate 7's three-call sequence (cell write → brand block → combined verification) applies here too.If <RUNTIME> is local-file: build an operations payload and apply it:
uv run "${CLAUDE_PLUGIN_ROOT}/scripts/write_workbook.py" --stdin <<'JSON'
{
"workbook_path": "<DESTINATION_PATH>",
"operations": [ ... ]
}
JSON
write_cell / write_formula / set_format operations. Avoid create_sheet and write_range here — those are for carta-create-budget.create_sheet, write_cell, write_range, merge_cells, set_bold, set_format, set_column_width (Account col), autofit_columns (data cols) per references/tag-view.md §"Writing the workbook (local-file runtime)". Always issue write_cell for a period label before the merge_cells op for that same range. Do NOT include freeze_panes — same rule as Layouts A–D and the rest of the Carta budgeting skills.create_sheet, write_cell, write_range, merge_cells, set_bold, set_format, set_column_width (Vendor/Account col), autofit_columns (data cols) per references/vendor-view.md §"Writing the workbook (local-file runtime)". Always issue write_cell for a period label before the merge_cells op for that same range. Do NOT include freeze_panes.If <VENDOR_GROUPING> is collapsed or expanded (set at Gate 3b), run a 4th execute_office_js call after all three required calls pass verification. This call is separate from the three required calls — do not bundle it with cell writes, branding, or verification.
See references/vendor-view.md §"Collapse/expand grouping" for the exact code block. Substitute <PERIOD_LABEL> and <VENDOR_GROUPING> before running.
Local-file runtime: skip this step. Row outline grouping is not supported via write_workbook.py.
After the brand block runs for every tab, execute one execute_office_js that checks both currency format and logo geometry in a single context.sync(). This replaces the two separate passes (currency readback → branding check) previously required.
Two regressions this catches:
[$$-en-US]#,##0.00_);([$$-en-US]#,##0.00);"-" (USD). Never use bare $, _($*, or a quoted literal "$" — Excel strips quotes from stored format strings, leaving a bare $ that renders as the system currency symbol.shape.height = 48 misaligns the logo when the E1:E3 row band is taller or shorter than 48pt. Height must come from rows.height.Range.getImage() is forbidden here. The shape geometry (heightMatchesBand, leftMatchesBand) is the complete, sufficient logo verification. Never output "I cannot visually verify the logo placement" — the geometry check IS the verification. If you find yourself reaching for Range.getImage(), stop and use the geometry check instead.
This block is NOT paste-verbatim — substitute its placeholders before running: the tabs array (the tab names touched this run) and <sample_amount_cell>. The currencyOk check uses "[$" verbatim — no substitution needed.
const tabs = [/* "Budget 2026", "2026 Actuals", ... — substitute the actual tab names touched this run */];
const result = {};
for (const tabName of tabs) {
const sheet = context.workbook.worksheets.getItem(tabName);
sheet.shapes.load("items/name,items/height,items/left,items/top");
const rows = sheet.getRange("E1:E3");
rows.load(["height", "left"]);
// Pick one amount cell — typically C7 (Layout E) or B8 (Layouts A–D). Substitute from your payload.
const cell = sheet.getRange("<sample_amount_cell>");
cell.load("numberFormat");
await context.sync();
const logo = sheet.shapes.items.find(s => s.name === "CartaLogo");
result[tabName] = {
// Currency check
numberFormat: cell.numberFormat[0][0],
currencyOk: cell.numberFormat[0][0].includes("[$"), // locale-specific currency token, e.g. [$$-en-US]
// Branding checks
found: !!logo,
shapeHeight: logo ? logo.height : null,
rowBandHeight: rows.height,
heightMatchesBand: logo ? Math.abs(logo.height - rows.height) < 2 : false,
shapeLeft: logo ? logo.left : null,
rowBandLeft: rows.left,
leftMatchesBand: logo ? Math.abs(logo.left - rows.left) < 2 : false,
};
}
return result;
Per-tab pass criteria — ALL must be true:
currencyOk === true — sample cell format contains [$ (locale-specific currency token)found === true — CartaLogo shape existsheightMatchesBand === true — logo height equals E1:E3 row-band height ±2ptleftMatchesBand === true — logo anchors at column E's left edge ±2ptRecovery actions:
currencyOk: false → re-apply the locale-specific token for the resolved currency — pick the matching line, substitute <full_amount_range>, then re-run this combined verification:
sheet.getRange("<full_amount_range>").numberFormat = [["[$$-en-US]#,##0.00_);([$$-en-US]#,##0.00);\"-\""]];sheet.getRange("<full_amount_range>").numberFormat = [["[$€-x-euro2]#,##0.00_);([$€-x-euro2]#,##0.00);\"-\""]];sheet.getRange("<full_amount_range>").numberFormat = [["[$£-en-GB]#,##0.00_);([$£-en-GB]#,##0.00);\"-\""]];sheet.getRange("<full_amount_range>").numberFormat = [["[$CA$-en-CA]#,##0.00_);([$CA$-en-CA]#,##0.00);\"-\""]];found: false → brand block was skipped — re-run the verbatim brand block, then re-verify.heightMatchesBand: false or leftMatchesBand: false → brand block used a hardcoded pixel or wrong anchor — delete the CartaLogo shape and re-run the verbatim brand block, then re-verify.Do not start Gate 8 summary text until every tab passes all four criteria. The verification call is observable evidence; without it in your tool history with passing checks, Gate 7 is not complete.
Gate 8 precondition (DO NOT SKIP). Before sending the summary text below, scan your tool history. Three anchors MUST be present in this order (excel-addin runtime):
AskUserQuestion whose answer included "Approve and apply the updates" — Gate 6 approval.sheet.shapes.addImage(base64) call for each tab the skill touched (one per tab) — Gate 7 branding.execute_office_js whose result showed currencyOk: true, found: true, heightMatchesBand: true, and leftMatchesBand: true for every tab — Gate 7 combined verification.If any anchor is missing, you have skipped a gate. Do NOT write "Carta logo placed at..." in the summary when no shapes.addImage call appears in your tool history — that's hallucinating completion. STOP, go back, run the missing gate, then return here.
Layouts A–D — If <RUNTIME> is excel-addin:
Refreshed 23 lines on Budget 2026 (Example MgmtCo). 2 lines zeroed (Audit, Tax Prep — no Q1 activity). 1 new account inserted under Operating Expenses (AI Tooling). 2 suspicious-zero flags — Salaries and Leased-employee payments dropped to $0; could be posting lag.
Layouts A–D — If <RUNTIME> is local-file:
Refreshed 23 lines on
Budget 2026infile:///path/to/<budget-workbook>.xlsx(Example MgmtCo). 2 lines zeroed (Audit, Tax Prep — no Q1 activity). 1 new account inserted (AI Tooling, Operating Expenses). 2 suspicious-zero flags — Salaries and Leased-employee payments.
Layout E — If <RUNTIME> is excel-addin:
Created 2026 Actuals by Department (Example MgmtCo) — 23 accounts × 4 department values (Engineering, Marketing, G&A, Untagged), annual aggregation. 1 account flagged low-confidence (sparse history). Carta logo placed at E1.
Substitute the period block phrasing to match
<AGGREGATION>from Gate 3a: "annual aggregation" forYEAR, "across 4 quarters" / "across Q1+Q2" forQUARTER, "across 12 months" / "across Jan–May" forMONTH.
Layout E — If <RUNTIME> is local-file:
Created
2026 Actuals by Departmenttab infile:///path/to/<budget-workbook>.xlsx(Example MgmtCo) — 23 accounts × 4 department values, annual aggregation. 1 account flagged low-confidence (sparse history). Adjust the period phrasing to match<AGGREGATION>(see excel-addin example above).
Layout F — If <RUNTIME> is excel-addin:
Created 2026 Actuals by Vendor (Example MgmtCo) — 23 accounts across 8 vendors (A2Z, Rippling, Alcatraz LLP, … No vendor), annual aggregation. 1 account flagged low-confidence (sparse history). Carta logo placed at E1.
Substitute the period block phrasing to match
<AGGREGATION>from Gate 3a: "annual aggregation" forYEAR, "across 4 quarters" / "across Q1+Q2" forQUARTER, "across 12 months" / "across Jan–May" forMONTH. List the top 3–5 vendor names; collapse the rest as "… No vendor".If
<VENDOR_GROUPING>iscollapsedorexpanded, append: "Vendor detail rows are grouped — use the +/− toggles on the left margin or the 1/2 outline buttons in the top-left corner to expand or collapse all vendor sections at once."
Layout F — If <RUNTIME> is local-file:
Created
2026 Actuals by Vendortab infile:///path/to/<budget-workbook>.xlsx(Example MgmtCo) — 23 accounts across 8 vendors, annual aggregation. 1 account flagged low-confidence (sparse history). Adjust the period phrasing to match<AGGREGATION>(see excel-addin example above).
Layout H — If <RUNTIME> is excel-addin:
Created 2026 Vendors (Example MgmtCo) — 8 vendors (A2Z, Rippling, Alcatraz LLP, … No vendor), annual aggregation. 1 vendor flagged low-confidence (sparse history). Carta logo placed at E1.
Substitute the period block phrasing to match
<AGGREGATION>from Gate 3a: "annual aggregation" forYEAR, "across 4 quarters" / "across Q1+Q2" forQUARTER, "across 12 months" / "across Jan–May" forMONTH. List the top 3–5 vendor names; collapse the rest as "… No vendor".
Layout H — If <RUNTIME> is local-file:
Created
2026 Vendorstab infile:///path/to/<budget-workbook>.xlsx(Example MgmtCo) — 8 vendors, annual aggregation. 1 vendor flagged low-confidence (sparse history). Adjust the period phrasing to match<AGGREGATION>(see excel-addin example above).
If Gate 5.5 ran and inferences were applied, append one sentence to the summary (Layout F/G/H): "<M> entries were inferred from memos and folded into their vendors (flagged with a cell comment); <residual> stayed as 'No vendor'." Do not claim any inference when <INFERRED_VENDORS> is empty.
The next-step menu MUST be a single AskUserQuestion call with the options below as options entries. Never render them as a numbered markdown list, a bulleted list, or inline prose — bare-text menus break the chooser UI in Claude for Excel and force the user to type the number. The ← recommended marker goes inside the description field of one option, not as a suffix on the label.
Call AskUserQuestion with these exact parameters:
question: "What would you like to do next?"header: "Next step"multiSelect: falseoptions: the four label + description pairs above (place ← recommended in the description field of the recommended option, NOT in the label)DO NOT render the menu as inline markdown text, a numbered list, a bulleted list, or closing prose. If your response is about to contain 1. ..., 2. ..., 3. ..., 4. ... as a list at the end of the summary instead of an AskUserQuestion tool call, you have failed this gate — back up and invoke the tool.
Mark ← recommended based on context — option 1 by default after a refresh; option 2 if the user previously asked about a specific line.
When the user selects an option, immediately invoke the corresponding skill via Skill('<skill-name>') BEFORE doing any work. Do not freelance the output — load the downstream skill's SKILL.md so its gates, layout spec, branding rules, and approval flow apply. Routing:
| Option | Skill to invoke |
|---|---|
| 1 — Run a pacing analysis | Skill('carta-investors:carta-budget-analysis') |
| 2 — Drill into a specific line item | Skill('carta-investors:carta-budget-analysis') with the drill-down-line reference |
| 3 — Model a what-if scenario | Skill('carta-investors:carta-budget-scenarios') |
| 4 — I'm done | No invocation; close cleanly |
Queries > 50 rows: request format: "ndjson", bucket into a blob. Don't paste large results — triggers context_snip. Use "markdown" only for ≤50-row previews.
carta-create-budget — Carta DWH journal-entries table only, no external-DWH fallback, FUND_NAME scoping, AMOUNT (not the base-currency variant), sign flip, preserve reversals.read_workbook.py output. Subtotals / NOI keep their =SUM(...) semantics.Budget/Actual/Variance triplet. Row N+1 = sub-headers spelled out in full (Budget, Actual, Variance). Never abbreviate to B/A/V. Never write both into the same row — subsequent merges destroy sub-headers.range.merge(true) discards trailing cell values. Insert a new row first.numberFormat = [["@"]] (text format) to the entire header range first, then write the values. Without this, Excel auto-coerces "Jan 2026" → date serial 46023. This applies to row 6 column headers and any period-band rows (rows 6/7 in Layouts E and F). Applies to both execute_office_js (set range.numberFormat before range.values) and write_workbook.py (emit a set_format op with "@" before the write_cell ops for the header row).welcome, or the currency on the budget data); if it can't be resolved, ask the user. State the resolved currency in cell A4: Amounts in <resolved_currency>.[$$-en-US]#,##0.00_);([$$-en-US]#,##0.00);"-" for USD, [$€-x-euro2]#,##0.00_);([$€-x-euro2]#,##0.00);"-" for EUR, [$£-en-GB]#,##0.00_);([$£-en-GB]#,##0.00);"-" for GBP. Resolve the currency from the data — never default to USD. Do not use a bare $ or _($* format — Excel substitutes the system currency symbol for those. Do not use a quoted literal like "$" — Excel strips the quotes when storing the format, leaving a bare $ that renders as the system currency. Apply to all data ranges after the data write.style = "Continuous", then weight = "Thin". Never style: "Thin".execute_office_js block (Call 1), in this order — never a separate call: restore automatic calc → context.workbook.application.calculate(Excel.CalculationType.full) → sheet.getRange("A:AN").format.autofitColumns() (widen the range to the last amount column) → context.sync(). Recalc before autofit: without the forced recalc the =SUM(...) / variance / NOI cells stay at 0 and the accounting format shows - (forcing the user to edit+Enter each one); autofitting before the recalc sizes the amount columns to the dash so real figures overflow as ####. Never autofit a header-only range.references/branding-and-header.md for every tab. Per-skill overrides: metadata band in column A (not B), logo at column E. Asset access via blobs.getText("assets/...").The skill queries the Carta DWH journal-entries table. If column names are needed, look up the table via the Carta MCP DWH schema command once at Gate 0 — production schema is canonical. Don't embed column listings inline; the DWH contract can drift.
| Symptom | Likely cause | What to tell the user |
|---|---|---|
| No Carta MCP server found | The Carta connector isn't enabled in this session | "I can't see your Carta connector. Open Settings → Connectors in Claude, enable Carta, then ask me again." |
| Sheet has no recognisable header row | The budget layout uses non-date column headers | Surface what the headers look like and ask the user which row is the header and which columns are actuals. |
low-confidence — sparse history flagged on many rows |
Entity is new or sparsely posted | Surface the count in the preview and let the user decide whether to proceed. Don't auto-suppress. |
| Multiple budget tabs in the workbook | Ambiguous "the budget" | Ask the user which tab to update; do not silently pick one. |
| Cell the skill wants to write is a formula | Subtotal / NOI row | Surface the row and confirm; never silently overwrite a formula. |
| Local-file mode: file path is missing or unreadable | Wrong path supplied | Echo the path back and ask for the correct one. |
| Query times out | DWH load | Tell the user it's slow and offer to retry — never auto-retry. |
| Auth / permission error from the MCP | Carta session expired or lacks DWH access | Ask the user to reconnect Carta in Settings → Connectors. |
Connector shows as connected, but tool calls fail with McpAuthError or "tool not available" |
The MCP server's tool prefix doesn't match what this skill's allowed-tools enumerates. Re-auth is not the fix — see Gate 0 troubleshooting note below the table. |
"I'm reconnecting to your Carta workspace — one moment." |
Connector-mismatch troubleshooting (operator-facing, not user-facing). Re-run refresh_mcp_connectors to confirm which Carta connector is actually connected, then probe the matching prefix's welcome per the Gate 0 mapping. Never tell the user to re-auth without verifying the prefix mismatch first.
Never auto-retry a failed query.
原文・著作権は Anthropic および各プラグイン作者に帰属します。日本語訳は Claude API による自動翻訳です。