Skip to content

fix(ci): Add error propagation to skill dependency sync in copilot-setup-steps #946

@WilliamBerryiii

Description

@WilliamBerryiii

Summary

The copilot-setup-steps.yml workflow uses find .github/skills -name pyproject.toml -execdir uv sync \; to install Python dependencies for skills. GNU find does not propagate exit codes from -execdir commands — if uv sync fails for any pyproject.toml, find still returns exit code 0. This means broken or misconfigured Python skill dependencies will silently fail to install. The Copilot coding agent will then encounter runtime ImportError or ModuleNotFoundError with no prior indication of failure in the setup step output.

Additionally, the workflow's find command is missing the -type f flag that the devcontainer includes, which could cause unexpected behavior if a directory happened to match the -name pyproject.toml pattern.

Context

PR #921 added uv installation and skill dependency sync to copilot-setup-steps.yml, implementing the find -execdir pattern prescribed by issue #888. The pattern itself works correctly when all uv sync invocations succeed. The problem is exclusively about failure detection — there is no mechanism to surface or propagate uv sync failures.

Confirmed Behavior: find -execdir Does Not Propagate Exit Codes

This was verified via direct testing on GNU find (Ubuntu/WSL):

$ mkdir -p /tmp/test/a /tmp/test/b
$ touch /tmp/test/a/pyproject.toml /tmp/test/b/pyproject.toml
$ find /tmp/test -name pyproject.toml -execdir false \;
$ echo $?
0

Even though false always returns exit code 1, find returns 0. This is documented GNU find behavior — the exit code of find reflects whether find itself completed its traversal successfully, not whether the executed commands succeeded.

The GitHub Actions shell default (bash --noprofile --norc -eo pipefail) means set -e is active, but this is irrelevant because find's return code is 0 regardless of -execdir failures. The step will always pass, even when uv sync fails.

Current State vs Devcontainer

Workflow (copilot-setup-steps.yml, line 89):

find .github/skills -name pyproject.toml -execdir uv sync \;

Devcontainer (on-create.sh, line 95):

find .github/skills -name pyproject.toml -type f -execdir uv sync \;

Note the devcontainer includes -type f to restrict matches to regular files. The workflow omits this flag. While both commands share the same silent-failure behavior with -execdir, the -type f flag should be added for robustness regardless.

Current Impact

None today — zero skills currently have pyproject.toml files. All existing skills under .github/skills/ contain only .md, .ps1, .sh, .psm1, and .Tests.ps1 files. However, issue #868 tracks the first Python skill (PowerPoint automation), and when that or any future Python skill lands, this code path becomes active and the silent failure risk becomes real.

Changes Required

File Change
.github/workflows/copilot-setup-steps.yml Replace find -execdir with loop that propagates uv sync exit codes
.github/workflows/copilot-setup-steps.yml Add -type f flag to the find command for robustness

Recommended Implementation

Replace the single find-execdir line with a loop that captures and propagates failures:

echo "Syncing Python environments for skills..."
failed=0
while IFS= read -r -d '' f; do
  dir="$(dirname "$f")"
  echo "Installing dependencies in $dir"
  if ! (cd "$dir" && uv sync); then
    echo "::error::uv sync failed in $dir"
    failed=1
  fi
done < <(find .github/skills -name pyproject.toml -type f -print0)
if [[ "$failed" -ne 0 ]]; then
  echo "::error::One or more skill dependency installations failed"
  exit 1
fi

This approach:

  • Uses -print0 / read -d '' for safe handling of paths with spaces or special characters
  • Includes -type f to match only regular files (parity with devcontainer)
  • Emits ::error:: annotations visible in the GitHub Actions UI per-directory failure
  • Continues processing remaining skills even when one fails (reports all failures, not just the first)
  • Exits with non-zero status if any uv sync invocation fails

Alternative: Minimal Fix

If the loop approach is considered too verbose, a simpler alternative adds error propagation to the devcontainer pattern as well:

find .github/skills -name pyproject.toml -type f -print0 | \
  xargs -0 -I {} bash -c 'cd "$(dirname "{}")" && uv sync'

However, this stops at the first failure and doesn't provide per-directory annotations. The loop approach is preferred.

Devcontainer Consideration

The devcontainer (on-create.sh, line 95) has the same silent-failure bug with -execdir. However, the devcontainer has set -euo pipefail at the top and is used interactively where failures are quickly noticed. A separate fix for on-create.sh may be warranted but is out of scope for this issue.

Acceptance Criteria

  • uv sync failures produce a non-zero exit code from the install step
  • Each skill directory with a failed uv sync produces a visible ::error:: annotation in the Actions log
  • The -type f flag is present on the find command
  • All matching pyproject.toml files are processed even when one fails (no early abort)
  • Paths with spaces or special characters are handled safely
  • The fix is verified by temporarily injecting a broken pyproject.toml in a test run (manual verification step)

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestinfrastructureRepository infrastructure and tooling

    Type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions