Skip to content

[WIP] Fix issues causing deploy-book failures#2

Draft
Copilot wants to merge 2 commits intomainfrom
copilot/fix-deploy-book-errors
Draft

[WIP] Fix issues causing deploy-book failures#2
Copilot wants to merge 2 commits intomainfrom
copilot/fix-deploy-book-errors

Conversation

Copy link
Copy Markdown

Copilot AI commented Nov 6, 2025

Fix deploy-book workflow to prevent EISDIR errors and build failures

This PR addresses the failing deploy-book GitHub Actions job (ref: https://github.com/AnthonyTSV/DatZB011/actions/runs/19132032446/job/54674903331, workflow ref: 92e930b)

Completed Changes

  • Understand the current workflow and identify issues
  • Replace .github/workflows/deploy.yml with robust implementation that:
    • Removes existing _jb_src before rsync to prevent recursion
    • Excludes .git and _jb_src in rsync to avoid copying metadata
    • Guards against reading non-file entries in Python conversion script
    • Adds check for _config.yml and _toc.yml presence
    • Adds detection for problematic .md directories that cause EISDIR
    • Pins jupyter-book to 0.13.* and installs myst-parser
    • Adds verbose logging to build step
    • Adds better failure diagnostics
  • Verify the changes are minimal and focused
  • Test all workflow steps locally

Key Improvements

  1. Prevent rsync recursion: Added rm -rf _jb_src before rsync and excluded .git and _jb_src directories
  2. Guard against EISDIR: Added is_file() check and exception handling in the conversion script
  3. Pin dependencies: Pinned jupyter-book to 0.13.* and added myst-parser
  4. Pre-build validation: Added checks for required config files and problematic .md directories
  5. Better diagnostics: Added verbose logging (-v) to build command and improved error messages

All changes have been tested locally and validated for syntax correctness.

Original prompt

Problem

The GitHub Actions job deploy-book is failing when running "jupyter-book build _jb_src" with errors like:

  • Error: EISDIR: illegal operation on a directory, read
  • No file exports found
  • Build failed: _jb_src/_build/html does not exist.

Investigation

I inspected the workflow file at .github/workflows/deploy.yml (ref: 92e930b) and found these fragile behaviours:

  • The Prepare working tree step uses: rsync -a --exclude '_build' ./ ./_jb_src/ which can cause recursion or copy .git and other metadata into the destination. If _jb_src already exists, rsync may copy into itself, producing odd file/directory artifacts and leading to EISDIR when tools expect files.
  • The Python conversion script iterates over Path.rglob("*.md") and does md.read_text() / md.write_text() without checking md.is_file() — if a directory or symlink named *.md exists, read_text will raise EISDIR.
  • The Install deps step installs the latest jupyter-book; different CLI behaviours across versions can lead to unexpected errors. The build step runs without verbose logging or preflight checks for required config files.

Goal

Produce a repository patch that makes the workflow robust, prevents recursion, guards the conversion step against reading non-files, pins a compatible jupyter-book version, and adds pre-build checks and better diagnostic output so the CI can either succeed or show clear, actionable errors.

Changes to make

  1. .github/workflows/deploy.yml: Replace the problematic steps with safer implementations:

    • Prepare working tree: remove any existing _jb_src, rsync from repo root to _jb_src while excluding .git and _jb_src to avoid recursion.
    • Convert GitHub alert blocks to MyST: change the Python script to skip non-files and print skipped paths.
    • Add a "Check _jb_src content" step to ensure _config.yml and _toc.yml are present and detect directories with .md suffix (likely cause of EISDIR).
    • Pin jupyter-book to a stable 0.13.* release and install myst-parser, and print the jupyter-book version.
    • Build Jupyter Book with verbose logging and better failure diagnostics.
  2. No other repository files need content changes.

Proposed new .github/workflows/deploy.yml content

(Replace the current file with the following content)


name: deploy-book
on:
push:
branches: [main, master]

permissions:
pages: write
id-token: write
contents: read

jobs:
deploy-book:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

  - name: Set up Python 3.11
    uses: actions/setup-python@v5
    with:
      python-version: "3.11"

  - name: Install deps
    run: |
      pip install -U 'jupyter-book==0.13.*' myst-parser
      jupyter-book --version

  # Make a temp working copy so originals stay untouched
  - name: Prepare working tree
    run: |
      rm -rf _jb_src
      rsync -a --exclude '_build' --exclude '.git' --exclude '_jb_src' ./ _jb_src/

  - name: List files in _jb_src
    run: ls -R _jb_src

  # Convert >[!NOTE] to ```{note} ... ``` (skip non-file md entries)
  - name: Convert GitHub alert blocks to MyST
    run: |
      python - <<'PY'
      from pathlib import Path
      import re, sys
      ROOT = Path('_jb_src')
      ALERT_START = re.compile(r'^\s*>\s*\[!(\w+)\]\s*(.*)$')
      QUOTE_LINE  = re.compile(r'^\s*>\s?(.*)$')
      MAP = {"NOTE":"note","TIP":"tip","IMPORTANT":"important","WARNING":"warning","CAUTION":"warning"}
      def convert_text(t:str)->str:
          out, lines, i, n = [], t.splitlines(), 0, 0
          n = len(lines)
          while i < n:
              m = ALERT_START.match(lines[i])
              if not m:
                  out.append(lines[i]); i+=1; continue
              kind = MAP.get(m.group(1).upper(), "note")
              body = [m.group(2).rstrip()] if m.group(2).strip() else []
              i += 1
              while i < n and (q:=QUOTE_LINE.match(lines[i])):
                  body.append(q.group(1).rstrip()); i += 1
              while body and body[0]=="": body.pop(0)
              while body and body[-1]=="" : body.pop()
              out.append(f"```{{{kind}}}")
              out.extend(body or [""])
              out.append("```")
          return "\n".join(out)
      for md in ROOT.rglob("*.md"):
          if not md.is_file():
              print("Skipping non-file:", md)
              continue
          try:
              src = md.read_text(encoding="utf-8")
          except Exception as e:
              print(f"Failed to read {md}: {e}")
              raise
          md.write_text(convert_text(src), encoding="utf-8")
      PY

  - name: Check _jb_src content
    run: |
      echo "Files at top of _jb_src:"
      ls -la _jb_src | sed -n '1,200p'
      test -f _jb_src/_co...

This pull request was created as a result of the following prompt from Copilot chat.

Problem

The GitHub Actions job deploy-book is failing when running "jupyter-book build _jb_src" with errors like:

  • Error: EISDIR: illegal operation on a directory, read
  • No file exports found
  • Build failed: _jb_src/_build/html does not exist.

Investigation

I inspected the workflow file at .github/workflows/deploy.yml (ref: 92e930b) and found these fragile behaviours:

  • The Prepare working tree step uses: rsync -a --exclude '_build' ./ ./_jb_src/ which can cause recursion or copy .git and other metadata into the destination. If _jb_src already exists, rsync may copy into itself, producing odd file/directory artifacts and leading to EISDIR when tools expect files.
  • The Python conversion script iterates over Path.rglob("*.md") and does md.read_text() / md.write_text() without checking md.is_file() — if a directory or symlink named *.md exists, read_text will raise EISDIR.
  • The Install deps step installs the latest jupyter-book; different CLI behaviours across versions can lead to unexpected errors. The build step runs without verbose logging or preflight checks for required config files.

Goal

Produce a repository patch that makes the workflow robust, prevents recursion, guards the conversion step against reading non-files, pins a compatible jupyter-book version, and adds pre-build checks and better diagnostic output so the CI can either succeed or show clear, actionable errors.

Changes to make

  1. .github/workflows/deploy.yml: Replace the problematic steps with safer implementations:

    • Prepare working tree: remove any existing _jb_src, rsync from repo root to _jb_src while excluding .git and _jb_src to avoid recursion.
    • Convert GitHub alert blocks to MyST: change the Python script to skip non-files and print skipped paths.
    • Add a "Check _jb_src content" step to ensure _config.yml and _toc.yml are present and detect directories with .md suffix (likely cause of EISDIR).
    • Pin jupyter-book to a stable 0.13.* release and install myst-parser, and print the jupyter-book version.
    • Build Jupyter Book with verbose logging and better failure diagnostics.
  2. No other repository files need content changes.

Proposed new .github/workflows/deploy.yml content

(Replace the current file with the following content)


name: deploy-book
on:
push:
branches: [main, master]

permissions:
pages: write
id-token: write
contents: read

jobs:
deploy-book:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

  - name: Set up Python 3.11
    uses: actions/setup-python@v5
    with:
      python-version: "3.11"

  - name: Install deps
    run: |
      pip install -U 'jupyter-book==0.13.*' myst-parser
      jupyter-book --version

  # Make a temp working copy so originals stay untouched
  - name: Prepare working tree
    run: |
      rm -rf _jb_src
      rsync -a --exclude '_build' --exclude '.git' --exclude '_jb_src' ./ _jb_src/

  - name: List files in _jb_src
    run: ls -R _jb_src

  # Convert >[!NOTE] to ```{note} ... ``` (skip non-file md entries)
  - name: Convert GitHub alert blocks to MyST
    run: |
      python - <<'PY'
      from pathlib import Path
      import re, sys
      ROOT = Path('_jb_src')
      ALERT_START = re.compile(r'^\s*>\s*\[!(\w+)\]\s*(.*)$')
      QUOTE_LINE  = re.compile(r'^\s*>\s?(.*)$')
      MAP = {"NOTE":"note","TIP":"tip","IMPORTANT":"important","WARNING":"warning","CAUTION":"warning"}
      def convert_text(t:str)->str:
          out, lines, i, n = [], t.splitlines(), 0, 0
          n = len(lines)
          while i < n:
              m = ALERT_START.match(lines[i])
              if not m:
                  out.append(lines[i]); i+=1; continue
              kind = MAP.get(m.group(1).upper(), "note")
              body = [m.group(2).rstrip()] if m.group(2).strip() else []
              i += 1
              while i < n and (q:=QUOTE_LINE.match(lines[i])):
                  body.append(q.group(1).rstrip()); i += 1
              while body and body[0]=="": body.pop(0)
              while body and body[-1]=="" : body.pop()
              out.append(f"```{{{kind}}}")
              out.extend(body or [""])
              out.append("```")
          return "\n".join(out)
      for md in ROOT.rglob("*.md"):
          if not md.is_file():
              print("Skipping non-file:", md)
              continue
          try:
              src = md.read_text(encoding="utf-8")
          except Exception as e:
              print(f"Failed to read {md}: {e}")
              raise
          md.write_text(convert_text(src), encoding="utf-8")
      PY

  - name: Check _jb_src content
    run: |
      echo "Files at top of _jb_src:"
      ls -la _jb_src | sed -n '1,200p'
      test -f _jb_src/_config.yml || (echo "_config.yml missing in _jb_src" && exit 1)
      test -f _jb_src/_toc.yml || (echo "_toc.yml missing in _jb_src" && exit 1)

  - name: Detect problematic .md directories
    run: |
      python - <<'PY'
      from pathlib import Path
      bad=[]
      for p in Path('_jb_src').rglob('*'):
        if p.is_dir() and p.name.lower().endswith('.md'):
          bad.append(str(p))
        if p.is_symlink():
          try:
            if Path(p.resolve()).is_dir():
              bad.append(str(p)+" -> symlink to dir")
          except Exception:
            bad.append(str(p)+" -> symlink unresolved")
      if bad:
        print("Found problematic entries that may cause EISDIR:\")
        print('\n'.join(bad))
        raise SystemExit(2)
      print("No obvious .md-as-directory or symlink issues found.")
      PY

  - name: Build Jupyter Book
    run: |
      set -e
      jupyter-book build _jb_src -v
      if [ ! -d "_jb_src/_build/html" ]; then
        echo "Build failed: _jb_src/_build/html does not exist."
        ls -la _jb_src/_build || true
        exit 1
      fi

  - name: Upload artifact
    uses: actions/upload-pages-artifact@v3
    with:
      path: "_jb_src/_build/html"

  - name: Deploy to GitHub Pages
    id: deployment
    uses: actions/deploy-pages@v4

Testing & verification

  • The workflow should now avoid copying .git and avoid recursive copying of _jb_src. The conversion script will skip non-file entries so md.read_text won't hit EISDIR. The new detection step will fail early with a clear list of problematic paths if something odd exists.

Please create a branch, apply the patch to replace .github/workflows/deploy.yml with the proposed content, and open a pull request. The PR should include a short description referencing the failing job: https://github.com/AnthonyTSV/DatZB011/actions/runs/19132032446/job/54674903331 and the workflow ref: 92e930b.

If you want, I can also open the PR for you (create the branch and commit) — confirm and I will create the pull request with this patch.


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Co-authored-by: AnthonyTSV <93186473+AnthonyTSV@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants