Skip to content

[Bug] Provide better error logging on backlog refinement issues with ADO #162

@djm81

Description

@djm81

Describe the Bug

When running SpecFact backlog refinement writeback against Azure DevOps (specfact backlog refine ado ... --import-from-tmp --write), SpecFact may attempt to PATCH the field System.AcceptanceCriteria even when the target ADO project’s process template does not contain that field. Azure DevOps rejects the request with HTTP 400:

TF51535: Cannot find field System.AcceptanceCriteria.

This is particularly likely in organizations using a customized/non-standard ADO process template where acceptance criteria exists under a different reference name (e.g. Microsoft.VSTS.Common.AcceptanceCriteria) or is not present at all.

A second UX issue: even with --debug, the initial logs did not include the ADO response body and did not clearly identify which JSON Patch operation (which field) caused the failure. We had to locally instrument specfact_cli/adapters/ado.py to make the root cause visible.

To Reproduce

Steps to reproduce the behavior:

# 1) Export an item to a known file (Windows-friendly path)
specfact backlog refine ado \
  --ado-org myorg \
  --ado-project myproject \
  --state "New" \
  --iteration "myproject\2026\Sprint 2026-02" \
  --assignee "Dominikus Nold" \
  --limit 1 \
  --export-to-tmp \
  --tmp-file "C:\path\to\specfact-backlog-refine-YYYYMMDD-HHMM.md"

# 2) Optionally refine/edit the markdown content (description, acceptance criteria, metrics)

# 3) Import + write back (this triggers the ADO PATCH)
specfact --debug backlog refine ado \
  --ado-org myorg \
  --ado-project myproject \
  --state "New" \
  --iteration "myproject\2026\Sprint 2026-02" \
  --assignee "Dominikus Nold" \
  --limit 1 \
  --import-from-tmp \
  --tmp-file "C:\path\to\specfact-backlog-refine-YYYYMMDD-HHMM.md" \
  --write

Important prerequisite for reproducing the failure: the ADO project must not have the field System.AcceptanceCriteria in its process template. In our case, ADO returned TF51535 complaining that this field cannot be found.

Expected Behavior

  • SpecFact should not attempt to update ADO fields that do not exist in the target project/process template.
  • If a mapped ADO field does not exist, SpecFact should:
    • fall back to an available equivalent reference name (where possible), or
    • skip that field and warn clearly, or
    • fail fast with a clear, actionable error message.
  • When writeback fails, --debug should log the ADO response body and the JSON Patch operations/paths so users can quickly identify the failing field mapping.

Actual Behavior

  • Writeback fails with HTTP 400 and ADO error TF51535: Cannot find field System.AcceptanceCriteria.
  • Without additional instrumentation, the debug log did not make it obvious which patch operation/field caused the failure.

Environment

  • OS: Windows (PowerShell)
  • Python Version: (fill in; e.g. 3.11.x)
  • SpecFact CLI Version: 0.26.13
  • Installation Method: pip into a virtualenv (local venv)

Command Output

Include the full command output (with --verbose if applicable):

400 Client Error: Bad Request for url: https://dev.azure.com/<org>/<project>/_apis/wit/workitems/<id>?api-version=7.1
TF51535: Cannot find field System.AcceptanceCriteria.

(Exact URL/work item ID will differ per environment.)

Codebase Context (for brownfield issues)

Not applicable (this occurs in the Azure DevOps adapter writeback path).

Additional Context

Root cause / why this is confusing

Many ADO organizations use customized process templates. In such templates, some “common” fields (by reference name) may be missing. SpecFact’s mapping/preference behavior can result in attempting to write to a System.* field that isn’t present, even when a Microsoft.VSTS.* alternative exists.

In our case, the writeback patch included /fields/System.AcceptanceCriteria, which caused the 400.

Workaround that resolved the issue

Add a custom mapping in the repo (or user config) to prevent SpecFact from attempting to write to System.AcceptanceCriteria when it doesn’t exist.

Example approach used successfully:

  • In ado_custom.yaml, remap:
    • System.AcceptanceCriteria → a “sink”/ignored canonical key (so it won’t be selected for writeback)
    • ensure acceptance criteria maps to Microsoft.VSTS.Common.AcceptanceCriteria instead
    • similarly, override story points field selection if your process template differs

After applying this mapping, running the same refine/writeback without any temporary “mapping hack” succeeded, implying SpecFact correctly loaded the repo-level mapping.

Local debug modifications we made (for maintainers / proposed improvement)

To diagnose the failure, we locally modified specfact_cli/adapters/ado.py (installed package in our venv) to log more details when the ADO PATCH fails.

What we added (high-level):

  • On HTTP error during the PATCH request, log:
    • response.status_code
    • a safe/truncated portion of response.text (ADO error body, which contains the missing field message)
    • the JSON Patch operation paths being attempted (e.g. replace /fields/System.AcceptanceCriteria)

This immediately revealed the root cause and would significantly reduce time-to-diagnosis for others.

Suggested product improvements

  1. Pre-flight field existence validation: Query ADO field metadata and validate that all /fields/<referenceName> exist before PATCHing.
  2. Mapping fallback behavior: If multiple candidate ADO reference names exist for a canonical field, prefer the one that exists in the current project.
  3. Better error surface by default: When ADO returns 400, print the ADO response message and list the JSON patch paths/fields being written (at least under --debug).
  4. Windows temp directory handling: Ensure default temp output uses a platform-correct temp directory (or auto-creates the directory), rather than assuming /tmp exists.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions