How do I get my Azure DevOps Pipeline build to fail when my linting script returns an error?

this means your script "swallows" the exit code and exits normally. you need to add a check to your script that would catch the exit code of your npm run lint and exit with the same exit code, something like:

- script: |
    npm install
    npm run lint # Mapped to `eslint src` in package.json
    if [ $? -ne 0 ]; then
        exit 1
    fi
    npm run slint # `stylelint src` in package.json
    npm run build

You could also use an npm task. The default is to fail the build when there is an error. I had the same problem and the below worked for me:

- task: Npm@1
  displayName: 'Lint'
  inputs:
    command: 'custom'
    customCommand: 'run lint'

From the documentation for tasks:

- task: string  # reference to a task and version, e.g. "VSBuild@1"
  condition: expression     # see below
  continueOnError: boolean  # 'true' if future steps should run even if this step fails; defaults to 'false'
  enabled: boolean          # whether or not to run this step; defaults to 'true'
  timeoutInMinutes: number  # how long to wait before timing out the task