Skip to content

Fix PathNotFound error in msexports ETL ingestion on first run - #2247

Open
Michael Flanakin (flanakin) wants to merge 2 commits into
flanakin/v15-prepfrom
flanakin/2088-adf-pathnotfound
Open

Fix PathNotFound error in msexports ETL ingestion on first run#2247
Michael Flanakin (flanakin) wants to merge 2 commits into
flanakin/v15-prepfrom
flanakin/2088-adf-pathnotfound

Conversation

@flanakin

@flanakin Michael Flanakin (flanakin) commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

🛠️ Description

The "Get Existing Parquet Files" GetMetadata activity fails with PathNotFound when the destination folder doesn't exist yet (e.g. first ingestion for a scope/dataset/month combination). Reservation recommendation exports hit this most often because their destination path adds an extra exportName segment, but any export type can hit it on a first run.

Fixed in both places this pattern exists: msexports_ETL_ingestion (Microsoft.CostManagement/Exports/app.bicep) and the analytics ingestion ETL pipeline (Microsoft.FinOpsHubs/Analytics/app.bicep). The two call sites aren't failing for the same reason, though: Exports writes to a destination folder that legitimately may not exist yet on first run, so this is a genuine fix there. Analytics reads a source folder that a manifest file just landed in, so that folder exists by construction whenever the pipeline runs — the change there is diagnostic, replacing an opaque PathNotFound ADLS error with the pipeline's own IngestionFilesNotFound error code if the folder is ever missing for some other reason (e.g. a malformed folderPath). It does not make a missing ingestion folder a no-op.

Fix: add 'exists' to the GetMetadata activity's fieldList. Per ADF's GetMetadata docs, specifying exists makes the activity return exists: false instead of throwing when the path is missing — "If exists isn't specified in the field list, the Get Metadata activity fails if the object isn't found." The downstream Filter activity's items expression now checks .output.exists before reading .output.childItems, so a missing folder resolves to an empty list instead of propagating the failure.

This is the same idiom already used elsewhere in this codebase for "path may not exist yet" scenarios: Check Schema in Exports/app.bicep, and both GetMetadata activities in IngestionQueries/app.bicep (one of which — "Get Existing Parquet Files" — already combines exists + childItems exactly like this fix does).

Root cause verification (re: issue #2088 comment)

A prior comment on the issue did solid root-cause analysis but its line numbers had drifted and its proposed fix options were speculative (custom error handling / parent-folder existence checks / wrapping in an If Condition). Re-verified against the current code:

  • Microsoft.CostManagement/Exports/app.bicep: GetMetadata activity is at lines 1090-1122, Filter Out Current Exports at 1123-1147 (comment's estimate of ~1090-1122 / ~1123-1147 was accurate).
  • Microsoft.FinOpsHubs/Analytics/app.bicep: identical vulnerable pattern confirmed at lines 1668-1707 (comment said ~1667-1706, essentially correct).
  • The actual cleanest fix is ADF's documented, built-in behavior for this exact case (exists in fieldList), not a custom If Condition or Web-activity workaround — and this idiom was already present elsewhere in the repo, so this change makes the two vulnerable call sites consistent with the rest of the codebase rather than introducing a new pattern.

Fixes #2088

📷 Screenshots

Not applicable — backend ETL pipeline fix, no UI changes.

📋 Checklist

🔬 How did you test this change?

  • 🤏 Lint tests
  • 🤞 PS -WhatIf / az validate
  • 👍 Manually deployed + verified
  • 💪 Unit tests
  • 🙌 Integration tests
  • bicep build src/templates/finops-hub/modules/Microsoft.CostManagement/Exports/app.bicep --stdout — builds cleanly
  • bicep build src/templates/finops-hub/modules/Microsoft.FinOpsHubs/Analytics/app.bicep --stdout — builds cleanly
  • Verified compiled ARM JSON includes "exists" in both fieldList arrays
  • pwsh -Command "./src/scripts/Test-PowerShell.ps1 -Lint" — 3418/3418 passed
  • Manual end-to-end validation against a real deployment with no prior export data not yet performed — requesting automated CI deployment below

📦 Deploy to test?

  • Hubs + ADX (managed)
  • Hubs + Fabric (manual) — URI:
  • Hubs (manual)
  • Hubs (no data)
  • Workbooks
  • Alerts

🙋‍♀️ Do any of the following that apply?

  • 🚨 This is a breaking change.
  • 🤏 The change is less than 20 lines of code.

📑 Did you update docs/changelog.md?

  • ✅ Updated changelog (required for dev PRs)
  • ➡️ Will add log in a future PR (feature branch PRs only)
  • ❎ Log not needed (small/internal change)

📖 Did you update documentation?

  • ✅ Public docs in docs (required for dev)
  • ✅ Public docs in docs-mslearn (required for dev)
  • ✅ Internal dev docs in docs-wiki (required for dev)
  • ✅ Internal dev docs in src (required for dev)
  • ➡️ Will add docs in a future PR (feature branch PRs only)
  • ❎ Docs not needed (small/internal change)

The "Get Existing Parquet Files" GetMetadata activity in both
msexports_ETL_ingestion (Microsoft.CostManagement/Exports/app.bicep)
and the analytics ingestion ETL pipeline
(Microsoft.FinOpsHubs/Analytics/app.bicep) failed with PathNotFound
when the destination folder had never been created, e.g. the first
ingestion for a scope/dataset/month combination. Recommendation
exports hit this most often because their path includes an extra
exportName segment, but any export type can hit it on first run.

Add 'exists' to the GetMetadata fieldList, which is ADF's documented
way to make the activity return exists:false instead of failing when
the path is missing. This mirrors the pattern already used by other
GetMetadata activities in this codebase (Check Schema in Exports/app.bicep,
and the two GetMetadata activities in IngestionQueries/app.bicep).
Update the downstream Filter activity's items expression to check
.output.exists before reading childItems, so a missing folder now
resolves to an empty file list instead of failing the pipeline.

Fixes #2088

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Change the 'Filter Out Current Exports' activity's dependency on
'Get Existing Parquet Files' from 'Completed' to 'Succeeded' in
Exports/app.bicep.

ADF's 'Completed' condition means succeeded OR failed. Now that
'exists' is in the fieldList (#2088 fix), a missing folder resolves
as a clean 'Succeeded' result, so 'Completed' no longer serves a
purpose for that case. Its only remaining effect was to let genuine
failures (permissions errors, wrong storage account, throttling)
silently flow through as if there were no existing files, which
would prevent superseded parquet files from being deleted and cause
duplicated data in reports with a green pipeline run.

This matches the Analytics/app.bicep version of the same downstream
filter activity, and the precedent in IngestionQueries/app.bicep,
both of which already use 'Succeeded'.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-reviewed at 73a0076. Both of my threads are settled — the dependency-condition change is a better fix than what I suggested, and the Analytics hunk holds up once you account for how the pipeline is triggered. Details in the threads.

Net change against dev is +5/-3 across two files, and the three Get Existing Parquet Files call sites (Exports, Analytics, IngestionQueries) now use an identical Succeeded + exists + contains shape, which is worth more than the individual hunks.

One non-blocking ask left in the second thread: the description implies both call sites were failing the same way, and they weren't — Exports genuinely hits a missing destination folder on first run, Analytics doesn't. A sentence there would save the next reader the trace. Approving regardless.

@microsoft-github-policy-service microsoft-github-policy-service Bot added the Micro PR 🔬 Very small PR that should be especially easy for newcomers label Sep 1, 2026
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Hubs (no data) deployment failed. View logs.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Holding for validation

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Hubs + ADX (managed) deployment failed. View logs.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Hubs (manual) deployment failed. View logs.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Hubs + ADX (managed) deployment failed. View logs.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Hubs (manual) deployment failed. View logs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Micro PR 🔬 Very small PR that should be especially easy for newcomers Skill: Deployment Resource deployment automation via bicep or terraform Status: ▶️ Ready Issue is ready for a dev to start work Tool: FinOps hubs Data pipeline solution

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[ADF] msexports_ETL_ingestion fails with PathNotFound for reservation recommendation exports

6 participants