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
- Pre-flight field existence validation: Query ADO field metadata and validate that all
/fields/<referenceName> exist before PATCHing.
- Mapping fallback behavior: If multiple candidate ADO reference names exist for a canonical field, prefer the one that exists in the current project.
- 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).
- Windows temp directory handling: Ensure default temp output uses a platform-correct temp directory (or auto-creates the directory), rather than assuming
/tmp exists.
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 fieldSystem.AcceptanceCriteriaeven 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 instrumentspecfact_cli/adapters/ado.pyto make the root cause visible.To Reproduce
Steps to reproduce the behavior:
Important prerequisite for reproducing the failure: the ADO project must not have the field
System.AcceptanceCriteriain its process template. In our case, ADO returnedTF51535complaining that this field cannot be found.Expected Behavior
--debugshould log the ADO response body and the JSON Patch operations/paths so users can quickly identify the failing field mapping.Actual Behavior
TF51535: Cannot find field System.AcceptanceCriteria.Environment
3.11.x)0.26.13Command Output
Include the full command output (with
--verboseif applicable):(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 aMicrosoft.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.AcceptanceCriteriawhen it doesn’t exist.Example approach used successfully:
System.AcceptanceCriteria→ a “sink”/ignored canonical key (so it won’t be selected for writeback)Microsoft.VSTS.Common.AcceptanceCriteriainsteadAfter 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):
response.status_coderesponse.text(ADO error body, which contains the missing field message)replace /fields/System.AcceptanceCriteria)This immediately revealed the root cause and would significantly reduce time-to-diagnosis for others.
Suggested product improvements
/fields/<referenceName>exist before PATCHing.--debug)./tmpexists.