Introduction

A script that runs successfully in a server terminal can fail in a CI/CD pipeline because the execution environment is different. The agent may use another account, a different shell, a restricted PATH, a separate working directory or different credentials. Successful interactive execution is not proof that the same script will work unattended.

When I investigate this problem, I compare those contexts before changing the script itself. A missing binary, an unexpected directory or an authentication prompt can explain a failure that initially looks mysterious. The goal is to make the required environment explicit rather than depend on the personal configuration of whoever tested the script.

This article explores what changes between a Linux server terminal and an Azure DevOps pipeline. It covers users, environment variables, permissions, network access and ephemeral state, followed by examples, a diagnostic checklist and a Bash template. These principles also apply to other CI/CD systems.

The intended audience includes developers, DevOps engineers and system administrators who need repeatable automation. Task automation with Makefile provides related background in Portuguese. The practical objective is to reduce assumptions, so scripts work reliably both locally and in a job with no human present to answer questions.

I do not assume that every pipeline has the same permissions or installed tools. Hosted and self-hosted agents have different contracts. A useful diagnosis identifies the actual account, image and task configuration involved, then checks the dependencies that the script really uses.

What changes between the terminal and a pipeline

AspectServer terminalPipeline
User/sessionYour interactive accountAgent or service account, usually noninteractive
ShellUser’s configured shell and profileShell invoked by the task; profile loading differs
EnvironmentLocal variables and customized PATHControlled job variables and a different PATH
Working directoryWhatever directory you openedAgent workspace or task-configured directory
PermissionsYour account’s permissions and possibly a TTYAgent permissions; usually no interactive TTY
DependenciesServer tools and installed versionsTools available on the selected agent/image
InteractionYou can answer promptsPrompts can hang or fail
NetworkServer routes, proxy and certificatesAgent routes, proxy and egress restrictions
FilesPersistent server pathsRepository workspace and pipeline artifacts
ResourcesHost limitsJob timeouts, quotas and concurrency limits
LogsLocal history, syslog or filesCentralized logs, secret masking and retention
StatePersistent machine stateHosted agents are typically ephemeral

Information: Use this table to identify concrete differences. A pipeline is not simply your terminal opened remotely: its account, directory and dependencies must be established separately.

Why the execution context matters

User and TTY

In a terminal, commands run as your user with a terminal device. Tools can detect the TTY and enable prompts, colors or interactive authentication. The pipeline agent normally runs without a person present. A read statement, a password prompt or an authentication wizard can therefore block the job.

The agent documentation describes the role of agents in executing jobs. Check the service identity and how that agent starts, rather than assuming it inherits the identity of the person who queued the build.

Environment and PATH

Your local profile may add /usr/local/bin, particular Node/.NET/Java versions, aliases and shell functions. None of those conveniences should be assumed in the agent. A tool can be absent, present under a different path or installed at another version. For example, changing the Node version can change dependency behavior even when the source script is identical.

Profiles also load differently between interactive, login and noninteractive shells. Do not treat .bashrc and .profile as interchangeable or assume that a task imports both. Initialize the required environment explicitly in automation.

Directories and relative paths

Scripts that interpret . as a particular directory or rely on an earlier cd are fragile. In Azure Pipelines, source files are normally found under $(Build.SourcesDirectory), while artifacts can be written under $(Build.ArtifactStagingDirectory). A task can also configure its working directory.

Warning: Never assume the current directory matches your terminal. Set the task’s working directory and use explicit paths for files that must be found reliably.

Permissions and security policies

Your interactive account may have sudo access or permissions that the agent account lacks. ACLs, SELinux and AppArmor can also affect access. A service account that cannot read a configuration or restart a service does not become authorized merely because the operation worked in your session.

Check permissions for the actual agent identity. Do not disable security policies to conceal an environment difference. A hosted build agent and a production server are different machines with different responsibilities.

Dependencies and versions

A successful server run may depend on packages installed months earlier. Hosted agents use a predefined image; self-hosted agents depend on how you maintain them. Unless the environments deliberately match, expect differences. Pin critical versions and declare dependencies so the script can fail with a useful message when something is missing.

Network, credentials and state

Registries and APIs may require proxy configuration, corporate certificates or permitted outbound routes. Credentials located under a user’s home directory, such as ~/.aws/credentials or ~/.npmrc, may not exist for the agent account. Use the pipeline’s supported secret mechanisms instead of copying personal credential files.

Ephemeral agents also invalidate assumptions about files left by earlier executions. Use managed artifacts and pipeline caching when the workflow needs data across jobs or runs. A temporary local file is not an artifact contract.

Practical examples

Sudo without an interactive terminal

The following command may ask for a password and fail in an unattended job:

1
sudo systemctl restart myservice

The original example authorizes one specific operation for the agent account:

1
vsts ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart myservice

This requires administrator review and is not a reason to give the agent unrestricted sudo access. Host service operations belong on an appropriate self-hosted deployment environment, not on an unrelated hosted build machine.

A tool available only in your PATH

1
jq '.version' package.json

If jq exists only on your server, the agent needs its own declared installation step or a job container that includes it. The source example uses Ubuntu package installation before running the command:

1
2
3
4
5
6
7
steps:
  - script: |
      sudo apt-get update && sudo apt-get install -y jq
    displayName: 'Install jq'
  - script: |
      jq '.version' package.json
    displayName: 'Run script with jq'

Do not apply this installation step indiscriminately to every agent type. It assumes a compatible package manager and the required privilege. A pinned container image is another way to make that dependency reproducible.

An invisible prompt

1
read -p "Continue? (y/n) " ans

An unattended job cannot answer this question. Use an explicit noninteractive mode and pipeline parameters instead of leaving the prompt in the job. The original example retains an optional terminal prompt:

1
2
3
4
5
SKIP_CONFIRM="${SKIP_CONFIRM:-1}"
if [[ "$SKIP_CONFIRM" != "1" ]]; then
    read -p "Continue? (y/n) " ans
    [[ "$ans" != "y" ]] && exit 0
fi

Tip: Separate interactive convenience from automated execution. An explicit noninteractive mode makes the contract visible, while destructive actions still require appropriate authorization outside the prompt.

Azure DevOps considerations

Hosted agents provide a Microsoft-managed, ephemeral environment with predefined tools. Self-hosted agents give you control over OS, versions and installed dependencies. Choose deliberately: owning the machine can improve parity, but also makes you responsible for maintenance and isolation.

For Bash scripts, use Bash@3 and configure workingDirectory when needed. Avoid relying on .bashrc; initialize the required environment within the script or task.

Pass credentials through secret variables or supported service connections. Authentication must be noninteractive and scoped to the operation. Related background on authentication and authorization is available in Portuguese.

Configure timeoutInMinutes so a blocked job does not run indefinitely. Use bounded retries with backoff for genuinely transient external failures. Increasing the timeout does not fix a permanent permission failure or a missing dependency.

Diagnostic checklist

  1. Identity: inspect id -u -n, whoami, the shell and whether a TTY exists.
  2. Environment: compare PATH and locale; inspect only non-sensitive variables. Do not dump secrets into job logs.
  3. Directory: check pwd, file paths and the task’s working directory.
  4. Tools: use command -v and version commands for critical binaries.
  5. Permissions: inspect authorized access for the service account. Do not execute a destructive command merely to test permissions.
  6. Network: verify the relevant endpoint, proxy and certificates without printing credentials.
  7. Logs: compare application and job errors, not only the final exit status.
  8. State: identify files that require persistence, caching or artifacts.

A short diagnostic step can reveal a version mismatch before the main work begins. Keep its output scoped: verbose logs and shell tracing can expose credentials even when the pipeline masks some registered secrets.

Tips and good practices

  • Standardize the environment. Use a pinned job container or deliberately maintained agent. The same source code is not enough if its runtime differs.
  • Declare dependencies. Check critical binaries and versions before starting work. Missing tools should produce an actionable failure, not an unrelated error later.
  • Avoid interaction. Use supported noninteractive flags and explicit parameters. Do not leave read, menus or authentication wizards in unattended jobs.
  • Manage credentials securely. Prefer secret variables and service connections. Avoid personal home-directory credential dependencies.
  • Use explicit paths. Set the working directory and resolve inputs against a known location. This removes a common source of terminal-only success.
  • Seek idempotence. Re-running the script should converge rather than break an already-correct state. Check the current state before modifying it.
  • Make failures visible. set -euo pipefail helps surface errors, but does not replace deliberate error handling. Use debug tracing only when it cannot expose sensitive values.

Bash template

The original article’s template illustrates environment normalization, dependency checks and bounded retries. It is a starting point, not a production deployment policy; review permissions, logging and the external endpoint before use.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/env bash
set -euo pipefail
[[ "${DEBUG:-0}" == "1" ]] && set -x
export PATH="/usr/local/bin:/usr/bin:/bin:$PATH"
WORKDIR="${WORKDIR:-$(pwd)}"
cd "$WORKDIR"
need() {
    command -v "$1" >/dev/null 2>&1 || {
        echo "ERROR: Missing '$1' in PATH. Install it first."
        exit 127
    }
}
need jq
need awk
NON_INTERACTIVE="${NON_INTERACTIVE:-1}"
retry() {
    local n=0
    until "$@"; do
        n=$((n + 1))
        [[ $n -ge 3 ]] && { echo "ERROR: Failed after 3 attempts: $*"; exit 1; }
        echo "Attempt $n failed. Waiting $((n * 2))s..."
        sleep $((n * 2))
    done
}

The source continues by passing an API token to curl. Do not enable shell tracing around secret-bearing calls, and do not include command arguments containing tokens in retry error messages. Those details need hardening before using the template with real credentials.

Objective Summary

  • Execution context includes the account, shell, working directory, dependencies and network; it can explain terminal/pipeline differences without a script logic bug.
  • Noninteractive jobs cannot reliably answer prompts and require an explicit unattended execution contract.
  • PATH and tool versions must be declared or checked, rather than inherited from personal profiles.
  • Ephemeral agents require managed artifacts or caches for state that must survive a job.
  • Security requires scoped credentials, reviewed permissions and logs that do not expose secrets.

Read Also

References