Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Azure DevOps Services | Azure DevOps Server 2022 - Azure DevOps Server 2019
This guide walks you through creating, testing, and publishing custom build or release tasks as Azure DevOps extensions. Custom pipeline tasks let you extend Azure DevOps with specialized functionality tailored to your team's workflows, from simple utilities to complex integrations with external systems.
Learn how to do the following tasks:
- Set up the development environment and project structure
- Create task logic using TypeScript and the Azure Pipelines Task Library
- Implement comprehensive unit testing with mock frameworks
- Package your extension for distribution
- Publish to the Visual Studio Marketplace
- Set up automated CI/CD pipelines for extension maintenance
For more information about Azure Pipelines, see What is Azure Pipelines?
Note
This article covers agent tasks in agent-based extensions. For information about server tasks and server-based extensions, see Server Task Authoring.
Prerequisites
Before you begin, ensure you have the following requirements in place:
Component | Requirement | Description |
---|---|---|
Azure DevOps organization | Required | Create an organization if you don't have one |
Text editor | Recommended | Visual Studio Code for IntelliSense and debugging support |
Node.js | Required | Install the latest version (Node.js 20 or later recommended) |
TypeScript compiler | Required | Install the latest version (version 4.6.3 or later) |
Azure DevOps CLI (tfx-cli) | Required | Install using npm i -g tfx-cli to package extensions |
Azure DevOps Extension SDK | Required | Install the azure-devops-extension-sdk package |
Testing framework | Required | Mocha for unit testing (installed during setup) |
Project structure
Create a home
directory for your project. After you complete this tutorial, your extension should have the following structure:
|--- README.md
|--- images
|--- extension-icon.png
|--- buildandreleasetask // Task scripts location
|--- task.json // Task definition
|--- index.ts // Main task logic
|--- package.json // Node.js dependencies
|--- tests/ // Unit tests
|--- _suite.ts
|--- success.ts
|--- failure.ts
|--- vss-extension.json // Extension manifest
Important
Your development machine must run the latest version of Node.js to ensure compatibility with the production environment. Update your task.json
file to use Node 20:
"execution": {
"Node20_1": {
"target": "index.js"
}
}
1. Create a custom task
This section guides you through creating the basic structure and implementation of your custom task. All files in this step should be created within the buildandreleasetask
folder inside your project's home
directory.
Note
This walkthrough uses Windows with PowerShell. The steps work on all platforms, but environment variable syntax differs. On Mac or Linux, replace $env:<var>=<val>
with export <var>=<val>
.
Set up the task scaffolding
Create the basic project structure and install required dependencies:
To initialize the Node.js project, open PowerShell, go to your
buildandreleasetask
folder, and run:npm init --yes
The
package.json
file gets created with default settings. The--yes
flag accepts all default options automatically.Tip
Azure Pipelines agents expect task folders to include node modules. Copy
node_modules
to yourbuildandreleasetask
folder. To manage VSIX file size (50-MB limit), consider runningnpm install --production
ornpm prune --production
before packaging.Install the Azure Pipelines Task Library:
npm install azure-pipelines-task-lib --save
Install TypeScript type definitions:
npm install @types/node --save-dev npm install @types/q --save-dev
Set up version control exclusions
echo node_modules > .gitignore
Your build process should run
npm install
to rebuild node_modules each time.Install testing dependencies:
npm install mocha --save-dev -g npm install sync-request --save-dev npm install @types/mocha --save-dev
Install TypeScript compiler:
npm install typescript@4.6.3 -g --save-dev
Note
Install TypeScript globally to ensure the
tsc
command is available. Without it, TypeScript 2.3.4 is used by default.Configure TypeScript compilation:
tsc --init --target es2022
The
tsconfig.json
file gets created with ES2022 target settings.
Implement the task logic
With scaffolding complete, create the core task files that define functionality and metadata:
Create the task definition file: Create
task.json
in thebuildandreleasetask
folder. This file describes your task to the Azure Pipelines system, defining inputs, execution settings, and UI presentation.{ "$schema": "https://raw.githubusercontent.com/Microsoft/azure-pipelines-task-lib/master/tasks.schema.json", "id": "{{taskguid}}", "name": "{{taskname}}", "friendlyName": "{{taskfriendlyname}}", "description": "{{taskdescription}}", "helpMarkDown": "", "category": "Utility", "author": "{{taskauthor}}", "version": { "Major": 0, "Minor": 1, "Patch": 0 }, "instanceNameFormat": "Echo $(samplestring)", "inputs": [ { "name": "samplestring", "type": "string", "label": "Sample String", "defaultValue": "", "required": true, "helpMarkDown": "A sample string" } ], "execution": { "Node20_1": { "target": "index.js" } } }
Note
Replace
{{placeholders}}
with your task's actual information. Thetaskguid
must be unique. Generate one using PowerShell:(New-Guid).Guid
To implement the task logic, create
index.ts
with your task's main functionality:import tl = require('azure-pipelines-task-lib/task'); async function run() { try { const inputString: string | undefined = tl.getInput('samplestring', true); if (inputString == 'bad') { tl.setResult(tl.TaskResult.Failed, 'Bad input was given'); return; } console.log('Hello', inputString); } catch (err: any) { tl.setResult(tl.TaskResult.Failed, err.message); } } run();
Compile TypeScript to JavaScript:
tsc
The
index.js
file gets created from your TypeScript source.
Understanding task.json components
The task.json
file is the heart of your task definition. Here are the key properties:
Property | Description | Example |
---|---|---|
id |
Unique GUID identifier for your task | Generated using (New-Guid).Guid |
name |
Task name without spaces (used internally) | MyCustomTask |
friendlyName |
Display name shown in the UI | My Custom Task |
description |
Detailed description of task functionality | Performs custom operations on files |
author |
Publisher or author name | My Company |
instanceNameFormat |
How the task appears in pipeline steps | Process $(inputFile) |
inputs |
Array of input parameters | See the following input types |
execution |
Execution environment specification | Node20_1 , PowerShell3 , etc. |
restrictions |
Security restrictions for commands and variables | Recommended for new tasks |
Security restrictions
For production tasks, add security restrictions to limit command usage and variable access:
"restrictions": {
"commands": {
"mode": "restricted"
},
"settableVariables": {
"allowed": ["variable1", "test*"]
}
}
Restricted mode allows only these commands:
logdetail
,logissue
,complete
,setprogress
setsecret
,setvariable
,debug
,settaskvariable
prependpath
,publish
Variable allowlist controls which variables can be set via setvariable
or prependpath
. Supports basic regex patterns.
Note
This feature requires agent version 2.182.1 or later.
Input types and examples
Common input types for task parameters:
"inputs": [
{
"name": "stringInput",
"type": "string",
"label": "Text Input",
"defaultValue": "",
"required": true,
"helpMarkDown": "Enter a text value"
},
{
"name": "boolInput",
"type": "boolean",
"label": "Enable Feature",
"defaultValue": "false",
"required": false
},
{
"name": "picklistInput",
"type": "pickList",
"label": "Select Option",
"options": {
"option1": "First Option",
"option2": "Second Option"
},
"defaultValue": "option1"
},
{
"name": "fileInput",
"type": "filePath",
"label": "Input File",
"required": true,
"helpMarkDown": "Path to the input file"
}
]
Test your task locally
Before packaging, test your task to ensure it works correctly:
Test with missing input (should fail):
node index.js
Expected output:
##vso[task.debug]agent.workFolder=undefined ##vso[task.debug]loading inputs and endpoints ##vso[task.debug]loaded 0 ##vso[task.debug]task result: Failed ##vso[task.issue type=error;]Input required: samplestring ##vso[task.complete result=Failed;]Input required: samplestring
Test with valid input (should succeed):
$env:INPUT_SAMPLESTRING="World" node index.js
Expected output:
##vso[task.debug]agent.workFolder=undefined ##vso[task.debug]loading inputs and endpoints ##vso[task.debug]loading INPUT_SAMPLESTRING ##vso[task.debug]loaded 1 ##vso[task.debug]samplestring=World Hello World
Test error handling:
$env:INPUT_SAMPLESTRING="bad" node index.js
This action should trigger the error handling path in your code.
Tip
For information about task runners and Node.js versions, see Node runner update guidance.
For more information, see the Build/release task reference.
2. Implement comprehensive unit testing
Testing your task thoroughly ensures reliability and helps catch issues before deployment to production pipelines.
Install testing dependencies
Install the required testing tools:
npm install mocha --save-dev -g
npm install sync-request --save-dev
npm install @types/mocha --save-dev
Create test
Create a
tests
folder in your task directory containing a_suite.ts
file:import * as path from 'path'; import * as assert from 'assert'; import * as ttm from 'azure-pipelines-task-lib/mock-test'; describe('Sample task tests', function () { before( function() { // Setup before tests }); after(() => { // Cleanup after tests }); it('should succeed with simple inputs', function(done: Mocha.Done) { // Success test implementation }); it('should fail if tool returns 1', function(done: Mocha.Done) { // Failure test implementation }); });
Tip
Your test folder should be located in the task folder (for example,
buildandreleasetask
). If you encounter a sync-request error, install it in the task folder:npm i --save-dev sync-request
.Create
success.ts
in your test directory to simulate successful task execution:import ma = require('azure-pipelines-task-lib/mock-answer'); import tmrm = require('azure-pipelines-task-lib/mock-run'); import path = require('path'); let taskPath = path.join(__dirname, '..', 'index.js'); let tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath); // Set valid input for success scenario tmr.setInput('samplestring', 'human'); tmr.run();
Add the success test to your
_suite.ts
file:it('should succeed with simple inputs', function(done: Mocha.Done) { this.timeout(1000); let tp: string = path.join(__dirname, 'success.js'); let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); tr.runAsync().then(() => { console.log(tr.succeeded); assert.equal(tr.succeeded, true, 'should have succeeded'); assert.equal(tr.warningIssues.length, 0, "should have no warnings"); assert.equal(tr.errorIssues.length, 0, "should have no errors"); console.log(tr.stdout); assert.equal(tr.stdout.indexOf('Hello human') >= 0, true, "should display Hello human"); done(); }).catch((error) => { done(error); // Ensure the test case fails if there's an error }); });
Create
failure.ts
in your test directory to test error handling:import ma = require('azure-pipelines-task-lib/mock-answer'); import tmrm = require('azure-pipelines-task-lib/mock-run'); import path = require('path'); let taskPath = path.join(__dirname, '..', 'index.js'); let tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath); // Set invalid input to trigger failure tmr.setInput('samplestring', 'bad'); tmr.run();
Add the failure test to your
_suite.ts
file:it('should fail if tool returns 1', function(done: Mocha.Done) { this.timeout(1000); const tp = path.join(__dirname, 'failure.js'); const tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); tr.runAsync().then(() => { console.log(tr.succeeded); assert.equal(tr.succeeded, false, 'should have failed'); assert.equal(tr.warningIssues.length, 0, 'should have no warnings'); assert.equal(tr.errorIssues.length, 1, 'should have 1 error issue'); assert.equal(tr.errorIssues[0], 'Bad input was given', 'error issue output'); assert.equal(tr.stdout.indexOf('Hello bad'), -1, 'Should not display Hello bad'); done(); }); });
Run your tests
Execute the test suite:
# Compile TypeScript
tsc
# Run tests
mocha tests/_suite.js
Both tests should pass. For verbose output (similar to build console output), set the trace environment variable:
$env:TASK_TEST_TRACE=1
mocha tests/_suite.js
Test coverage best practices
- Test all input combinations: Valid inputs, invalid inputs, missing required inputs
- Test error scenarios: Network failures, file system errors, permission issues
- Mock external dependencies: Don't rely on external services in unit tests
- Validate outputs: Check console output, task results, and generated artifacts
- Performance testing: Consider adding tests for tasks that process large files
Security best practices
- Input validation: Always validate and sanitize inputs
- Secrets handling: Use
setSecret
for sensitive data - Command restrictions: Implement command restrictions for production tasks
- Minimal permissions: Request only necessary permissions
- Regular updates: Keep dependencies and Node.js versions current
After testing your task locally and implementing comprehensive unit tests, package it into an extension for Azure DevOps.
Install packaging tools
Install the Cross Platform Command Line Interface (tfx-cli):
npm install -g tfx-cli
Create the extension manifest
The extension manifest (vss-extension.json
) contains all information about your extension, including references to your task folders and images.
Create an images folder with an
extension-icon.png
fileCreate
vss-extension.json
in your extension's root directory (not in the task folder):{ "manifestVersion": 1, "id": "my-custom-tasks", "name": "My Custom Tasks", "version": "1.0.0", "publisher": "your-publisher-id", "targets": [ { "id": "Microsoft.VisualStudio.Services" } ], "description": "Custom build and release tasks for Azure DevOps", "categories": [ "Azure Pipelines" ], "icons": { "default": "images/extension-icon.png" }, "files": [ { "path": "MyCustomTask" } ], "contributions": [ { "id": "my-custom-task", "type": "ms.vss-distributed-task.task", "targets": [ "ms.vss-distributed-task.tasks" ], "properties": { "name": "MyCustomTask" } } ] }
Key manifest properties
Property | Description |
---|---|
publisher |
Your marketplace publisher identifier |
contributions.id |
Unique identifier within the extension |
contributions.properties.name |
Must match your task folder name |
files.path |
Path to your task folder relative to the manifest |
Note
Change the publisher value to your publisher name. For information about creating a publisher, see Create your publisher.
Package your extension
Package your extension into a .vsix file:
tfx extension create --manifest-globs vss-extension.json
Version management
- Extension version: Increment the version in
vss-extension.json
for each update - Task version: Increment the version in
task.json
for each task update - Auto-increment: Use
--rev-version
to automatically increment the patch version
tfx extension create --manifest-globs vss-extension.json --rev-version
Important
Both the task version and extension version must be updated for changes to take effect in Azure DevOps.
Versioning strategy
Follow semantic versioning principles for your task updates:
- Major version: Breaking changes to inputs/outputs
- Minor version: New features, backward compatible
- Patch version: Bug fixes only
Update process:
- Update
task.json
version - Update
vss-extension.json
version - Test thoroughly in a test organization
- Publish and monitor for issues
Publish to Visual Studio Marketplace
1. Create your publisher
- Sign in to the Visual Studio Marketplace Publishing Portal
- Create a new publisher if prompted:
- Publisher identifier: Used in your extension manifest (for example,
mycompany-myteam
) - Display name: Public name shown in the marketplace (for example,
My Team
)
- Publisher identifier: Used in your extension manifest (for example,
- Review and accept the Marketplace Publisher Agreement
2. Upload your extension
Web interface method:
- Select Upload new extension
- Choose your packaged
.vsix
file - Select Upload
Command-line method:
tfx extension publish --manifest-globs vss-extension.json --share-with yourOrganization
3. Share your extension
- Right-click your extension in the marketplace
- Select Share
- Enter your organization name
- Add more organizations as needed
Important
Publishers must be verified to share extensions publicly. For more information, see Package/Publish/Install.
4. Install to your organization
After sharing, install the extension to your Azure DevOps organization:
- Navigate to Organization Settings > Extensions
- Browse for your extension
- Select Get it free and install
3. Package and publish your extension
Verify your extension
After installation, verify your task works correctly:
- Create or edit a pipeline.
- Add your custom task:
- Select Add task in the pipeline editor
- Search for your custom task by name
- Add it to your pipeline
- Configure task parameters:
- Set required inputs
- Configure optional settings
- Run the pipeline to test functionality
- Monitor execution:
- Check task logs for proper execution
- Verify expected outputs
- Ensure no errors or warnings
4. Automate extension publishing with CI/CD
To maintain your custom task effectively, create automated build and release pipelines that handle testing, packaging, and publishing.
Prerequisites for automation
- Azure DevOps Extension Tasks: Install the extension for free
- Variable group: Create a pipeline library variable group with these variables:
publisherId
: Your marketplace publisher IDextensionId
: Extension ID from vss-extension.jsonextensionName
: Extension name from vss-extension.jsonartifactName
: Name for the VSIX artifact
- Service connection: Create a Marketplace service connection with pipeline access permissions
Complete CI/CD pipeline
Create a YAML pipeline with comprehensive stages for testing, packaging, and publishing:
trigger:
- main
pool:
vmImage: "ubuntu-latest"
variables:
- group: extension-variables # Your variable group name
stages:
- stage: Test_and_validate
displayName: 'Run Tests and Validate Code'
jobs:
- job: RunTests
displayName: 'Execute unit tests'
steps:
- task: TfxInstaller@4
displayName: 'Install TFX CLI'
inputs:
version: "v0.x"
- task: Npm@1
displayName: 'Install task dependencies'
inputs:
command: 'install'
workingDir: '/MyCustomTask' # Update to your task directory
- task: Bash@3
displayName: 'Compile TypeScript'
inputs:
targetType: "inline"
script: |
cd MyCustomTask # Update to your task directory
tsc
- task: Npm@1
displayName: 'Run unit tests'
inputs:
command: 'custom'
workingDir: '/MyCustomTask' # Update to your task directory
customCommand: 'test' # Ensure this script exists in package.json
- task: PublishTestResults@2
displayName: 'Publish test results'
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '**/test-results.xml'
searchFolder: '$(System.DefaultWorkingDirectory)'
- stage: Package_extension
displayName: 'Package Extension'
dependsOn: Test_and_validate
condition: succeeded()
jobs:
- job: PackageExtension
displayName: 'Create VSIX package'
steps:
- task: TfxInstaller@4
displayName: 'Install TFX CLI'
inputs:
version: "v0.x"
- task: Npm@1
displayName: 'Install dependencies'
inputs:
command: 'install'
workingDir: '/MyCustomTask'
- task: Bash@3
displayName: 'Compile TypeScript'
inputs:
targetType: "inline"
script: |
cd MyCustomTask
tsc
- task: QueryAzureDevOpsExtensionVersion@4
name: QueryVersion
displayName: 'Query current extension version'
inputs:
connectTo: 'VsTeam'
connectedServiceName: 'marketplace-connection'
publisherId: '$(publisherId)'
extensionId: '$(extensionId)'
versionAction: 'Patch'
- task: PackageAzureDevOpsExtension@4
displayName: 'Package extension'
inputs:
rootFolder: '$(System.DefaultWorkingDirectory)'
publisherId: '$(publisherId)'
extensionId: '$(extensionId)'
extensionName: '$(extensionName)'
extensionVersion: '$(QueryVersion.Extension.Version)'
updateTasksVersion: true
updateTasksVersionType: 'patch'
extensionVisibility: 'private'
extensionPricing: 'free'
- task: PublishBuildArtifacts@1
displayName: 'Publish VSIX artifact'
inputs:
PathtoPublish: '$(System.DefaultWorkingDirectory)/*.vsix'
ArtifactName: '$(artifactName)'
publishLocation: 'Container'
- stage: Publish_to_marketplace
displayName: 'Publish to Marketplace'
dependsOn: Package_extension
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: PublishExtension
displayName: 'Deploy to marketplace'
environment: 'marketplace-production'
strategy:
runOnce:
deploy:
steps:
- task: TfxInstaller@4
displayName: 'Install TFX CLI'
inputs:
version: "v0.x"
- task: PublishAzureDevOpsExtension@4
displayName: 'Publish to marketplace'
inputs:
connectTo: 'VsTeam'
connectedServiceName: 'marketplace-connection'
fileType: 'vsix'
vsixFile: '$(Pipeline.Workspace)/$(artifactName)/*.vsix'
publisherId: '$(publisherId)'
extensionId: '$(extensionId)'
extensionName: '$(extensionName)'
updateTasksVersion: false
extensionVisibility: 'private'
extensionPricing: 'free'
Configure package.json for testing
Add test scripts to your package.json
:
{
"scripts": {
"test": "mocha tests/_suite.js --reporter xunit --reporter-option output=test-results.xml",
"test-verbose": "cross-env TASK_TEST_TRACE=1 npm test"
}
}
Pipeline stage breakdown
Stage 1: Test and validate
- Purpose: Ensure code quality and functionality
- Actions: Install dependencies, compile TypeScript, run unit tests, publish results
- Validation: All tests must pass to proceed
Stage 2: Package extension
- Purpose: Create deployable VSIX package
- Actions: Query current version, increment version, package extension, publish artifacts
- Versioning: Automatically handles version increments
Stage 3: Publish to marketplace
- Purpose: Deploy to Visual Studio Marketplace
- Conditions: Only runs on main branch after successful packaging
- Environment: Uses deployment environment for approval gates
Best practices for CI/CD
- Branch protection: Only publish from main/release branches
- Environment gates: Use deployment environments for production releases
- Version management: Automate version increments to avoid conflicts
- Test coverage: Ensure comprehensive test coverage before packaging
- Security: Use service connections instead of hardcoded credentials
- Monitoring: Set up alerts for failed deployments
For classic build pipelines, follow these steps to set up extension packaging and publishing:
Add the
Bash
task to compile the TypeScript into JavaScript.To query the existing version, add the Query Extension Version task using the following inputs:
- Connect to: Visual Studio Marketplace
- Visual Studio Marketplace (Service connection): Service Connection
- Publisher ID: ID of your Visual Studio Marketplace publisher
- Extension ID: ID of your extension in the
vss-extension.json
file - Increase version: Patch
- Output Variable:
Task.Extension.Version
To package the extensions based on manifest Json, add the Package Extension task using the following inputs:
- Root manifests folder: Points to root directory that contains manifest file. For example,
$(System.DefaultWorkingDirectory)
is the root directory - Manifest file:
vss-extension.json
- Publisher ID: ID of your Visual Studio Marketplace publisher
- Extension ID: ID of your extension in the
vss-extension.json
file - Extension Name: Name of your extension in the
vss-extension.json
file - Extension Version:
$(Task.Extension.Version)
- Override tasks version: checked (true)
- Override Type: Replace Only Patch (1.0.r)
- Extension Visibility: If the extension is still in development, set the value to private. To release the extension to the public, set the value to public.
- Root manifests folder: Points to root directory that contains manifest file. For example,
To copy to published files, add the Copy files task using the following inputs:
- Contents: All of the files to be copied for publishing them as an artifact
- Target folder: The folder that the files get copied to
- For example:
$(Build.ArtifactStagingDirectory)
- For example:
Add Publish build artifacts to publish the artifacts for use in other jobs or pipelines. Use the following inputs:
- Path to publish: The path to the folder that contains the files that are being published
- For example:
$(Build.ArtifactStagingDirectory)
- For example:
- Artifact name: The name given to the artifact
- Artifacts publish location: Choose Azure Pipelines to use the artifact in future jobs
- Path to publish: The path to the folder that contains the files that are being published
Stage 3: Download build artifacts and publish the extension
To install the tfx-cli onto your build agent, add Use Node CLI for Azure DevOps (tfx-cli).
To download the artifacts onto a new job, add the Download build artifacts task using the following inputs:
- Download artifacts produced by: If you're downloading the artifact on a new job from the same pipeline, select Current build. If you're downloading on a new pipeline, select Specific build
- Download type: Choose Specific artifact to download all files that were published.
- Artifact name: The published artifact's name
- Destination directory: The folder where the files should be downloaded
To get the Publish Extension task, use the following inputs:
- Connect to: Visual Studio Marketplace
- Visual Studio Marketplace connection: ServiceConnection
- Input file type: VSIX file
- VSIX file:
/Publisher.*.vsix
- Publisher ID: ID of your Visual Studio Marketplace publisher
- Extension ID: ID of your extension in the
vss-extension.json
file - Extension Name: Name of your extension in the
vss-extension.json
file - Extension visibility: Either private or public
Optional: Install and test your extension
After you publish your extension, it needs to be installed in Azure DevOps organizations.
Install extension to organization
Install your shared extension in a few steps:
Go to Organization settings and select Extensions.
Locate your extension in the Extensions Shared With Me section:
- Select the extension link
- Select Get it free or Install
Check that the extension appears in your Installed extensions list:
- Confirm it's available in your pipeline task library
Note
If you don't see the Extensions tab, ensure you're at the organization administration level (https://dev.azure.com/{organization}/_admin
) and not at the project level.
End-to-end testing
After installation, perform comprehensive testing:
Create a test pipeline:
- Add your custom task to a new pipeline
- Configure all input parameters
- Test with various input combinations
Validate functionality:
- Run the pipeline and monitor execution
- Check task outputs and logs
- Verify error handling with invalid inputs
Test performance:
- Test with large input files (if applicable)
- Monitor resource usage
- Validate timeout behavior
Frequently asked questions
Q: How is task cancellation handled?
A: The pipeline agent sends SIGINT
and SIGTERM
signals to task processes. While the task library doesn't provide explicit cancellation handling, your task can implement signal handlers. For details, see Agent jobs cancellation.
Q: How can I remove a task from my organization?
A: Automatic deletion isn't supported as it would break existing pipelines. Instead:
- Deprecate the task: Mark the task as deprecated
- Version management: Bump the task version
- Communication: Notify users about the deprecation timeline
Q: How can I upgrade my task to the latest Node.js version?
A: Upgrade to the latest Node version for better performance and security. For migration guidance, see Upgrading tasks to Node 20.
Support multiple Node versions by including multiple execution sections in task.json
:
"execution": {
"Node20_1": {
"target": "index.js"
},
"Node10": {
"target": "index.js"
}
}
Agents with Node 20 use the preferred version, while older agents fall back to Node 10.
To upgrade your tasks:
To ensure your code behaves as expected, test your tasks on the various Node runner versions.
In your task's execution section, update from
Node
orNode10
toNode16
orNode20
.To support older server versions, you should leave the
Node
/Node10
target. Older Azure DevOps Server versions might not have the latest Node runner version included.You can choose to share the entry point defined in the target or have targets optimized to the Node version used.
"execution": { "Node10": { "target": "bash10.js", "argumentFormat": "" }, "Node16": { "target": "bash16.js", "argumentFormat": "" }, "Node20_1": { "target": "bash20.js", "argumentFormat": "" } }
Important
If you don't add support for the Node 20 runner to your custom tasks, they fail on agents installed from the pipelines-agent-*
release feed.