Repository navigation
Work Items
tf-snag can raise an Azure Boards work item for each finding, and keep it up to date as the finding is ignored, reinstated or fixed.
export TF_SNAG_ADO_TOKEN=... # PAT or System.AccessToken
tf-snag -check all -plan plan.json -plan-log plan.jsonl -baseline prev.sarif \
-ado-url https://dev.azure.com/my-org/my-project -ado-type Task -run-url "$RUN_URL"In a pipeline, set the tf-snag@0 task's workItems input to dry-run, then on. Start with a dry run: -ado-dry-run (or workItems: dry-run) reports what would be raised and closed, and checks the token and permissions, without changing anything.

A work item raised by tf-snag. The tf-snag-id-… tag ties it to exactly one finding, and the description carries the drifted attributes, the finding's badges and a link back to the run.
Every item tf-snag creates is tagged tf-snag and tf-snag-id-<finding id>, using the finding's stable id. Each run queries those tags, so Azure DevOps itself is the record of what has been raised, not a build artifact that can expire. The id comes from the resource address alone, so a finding keeps its item as the drifted values change.
While an item for a finding is open, every run links to it and none raises a second, wherever it has been moved on the board. Reports carry the link (work_item and work_item_url in JSON), and the run tab shows it on the finding.
If a finding already has more than one open item, tf-snag tracks the oldest, names the others in the run output, and changes none of them. Which to keep is a decision for a person.
-ado-raise |
Raises for |
|---|---|
new (default) |
findings absent from -baseline, so adopting tf-snag against an estate full of drift does not open fifty items on day one |
findings |
anything not ignored |
An ignored finding never gets a new item: somebody has already decided not to act on it.
Ignoring a finding does not close its item. Closing would tell the board the work was done when nobody did any, and the item may already be triaged or in a sprint. tf-snag comments once instead, saying the drift is still there and is no longer reported, and tags the item tf-snag-ignored so the comment is not repeated. Whether to close it is left to a person.
Removing the ignore rule resumes on the same item. The comment is retracted, the tag removed, and the finding is badged No longer ignored. No second item is raised.
With -ado-close, an item whose finding is no longer reported moves to -ado-closed-state, with a comment saying why. The state defaults to the work item type's own Completed state: Closed on Agile, Done on Scrum and Basic. A state you supply is checked against the type's real states before anything is closed.
| Closing | |
|---|---|
| CLI | off by default. Pass -ado-close
|
tf-snag@0 task |
on by default (adoClose) |
Closing is opt-in on the CLI because raising items only adds, while closing changes work someone may have triaged or linked.
A closed item is history, not a slot to reuse. If fixed drift comes back, it is a new occurrence and gets a new item, which opens with a comment naming the closed one it follows. Any state the process counts as Completed or Removed counts as closed.
The token needs Work Items (Read & Write). Read is needed too, because finding existing items is a query.
Before changing anything, tf-snag makes a validate-only create. A bad token, project or work item type fails straight away, naming the problem, rather than halfway through a run. Azure DevOps answers an unauthenticated call with a sign-in page rather than a 401, and tf-snag reports that as "not authenticated".
-ado-token accepts a PAT or a pipeline's System.AccessToken; the type is detected from the token. Pass it through TF_SNAG_ADO_TOKEN rather than the command line. With System.AccessToken, the <Project> Build Service identity needs Edit work items in this node on the area path.
All work item output goes to stderr, so it never mixes into a report on stdout.
Start here
How it works
Integrations
Project