Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .github/scripts/docs-agent-prompt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
You are updating the Glific documentation site (glific/docs) in response to a GitHub issue.

Issue #${ISSUE_NUMBER}: ${ISSUE_TITLE}

${ISSUE_BODY}

Two related product repositories have already been cloned, read-only, as subdirectories
of this working directory:
- repos/glific (backend, Elixir)
- repos/glific-frontend (frontend, React)

Your task:

1. Investigate the issue above and, using the two repos, figure out what product
behavior, screen, or feature it refers to. Look at recent commits, relevant source
files, and README/CHANGELOG content in those repos for context.

2. Decide which existing doc page(s) under docs/ need updating, or whether a new page
is needed. Follow the structure, numbering, and page conventions documented in this
repo's CLAUDE.md.

3. Edit or create the minimal set of doc files needed, matching the conventions in
CLAUDE.md and the neighboring pages in the same folder.

4. Wherever the doc should show a screenshot of the actual running app, insert a single
placeholder line of this exact form (a later automated step replaces it with a real
image — do not invent or guess an image path yourself):

![](SCREENSHOT:<short-slug>:<app-route-path>)

- <short-slug> is a short kebab-case identifier, unique within this change (e.g.
"flow-editor-new-node").
- <app-route-path> is the in-app route to screenshot, starting with "/" (e.g.
"/flow/configure/123").
- Only add these where a screenshot genuinely helps the reader; no more than 3.
Comment on lines +25 to +35

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== tracked files =="
git ls-files | sed -n '1,120p'

echo
echo "== git diff stat =="
git diff --stat || true

echo
echo "== files of interest =="
for f in .github/scripts/docs-agent-prompt.md .github/scripts/take-screenshots.mjs; do
  if [ -f "$f" ]; then
    echo "--- $f ($(wc -l < "$f") lines) ---"
    cat -n "$f"
  else
    echo "MISSING $f"
  fi
done

echo
echo "== related scripts and usage =="
git ls-files .github/scripts | xargs -r -n1 sh -c 'echo "--- $1 ---"; rg -n "docs-agent-prompt|take-screenshots|screenshots|page.goto|SCREENSHOT|placeholder|route|ISSUE_NUMBER|STAGING_URL|console\\.log|mkdirSync|page\\.screenshot|placeholder|prompt" "$1" || true' sh {}

Repository: glific/docs

Length of output: 15911


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflows mentioning screenshot or docs agent =="
for f in .github/workflows/*.yml .github/workflows/*.yaml; do
  [ -f "$f" ] || true
done | while read -r f; do
  echo "--- $f ($(wc -l < "$f") lines) ---"
  rg -n "docs-agent|SCREENSHOT|take-screenshots|github_issue|issue|body|token|STAGING|GLIFIC|auto-docs|permissions|pull_request|workflow_run" "$f" || true
done

echo
echo "== config files likely containing staging credentials or relevant env =="
git ls-files | rg '(^|/)\.env|(^|/)\.env\.(sample|example|production|staging)|(^|/)docusaurus\.config|package\.json|CLAUDE\.md|README\.md' | while read -r f; do
  echo "--- $f ($(wc -l < "$f") lines) ---"
  rg -n "GLIFIC_STAGING|STAGING_URL|STAGING_PHONE|STAGING_PASSWORD|SCREENSHOT|take-screenshots|docs-agent|permissions|auto-docs|GitHub Apps|github_app|token" "$f" || true
done

echo
echo "== repository references to generated screenshots and auth =="
rg -n "generated/.*/.*\.png|generated/|static/img/generated|SCREENSHOT|GLIFIC_STAGING_PHONE|GLIFIC_STAGING_PASSWORD|password|phoneNumber|Login|SubmitButton|glific-frontend|glific/repo|GITHUB_TOKEN" .github README.md CLAUDE.md .env.example 2>/dev/null || true

Repository: glific/docs

Length of output: 4244


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== auto-docs workflow =="
cat -n .github/workflows/auto-docs.yml

echo
echo "== package scripts and relevant config =="
cat -n package.json
sed -n '1,80p' .github/scripts/take-screenshots.mjs
sed -n '115,145p' .github/scripts/take-screenshots.mjs

echo
echo "== static generated image references =="
git ls-files | rg '^static/img/generated/' || true
rg -n "static/img/generated|generated/" .github docs README.md CLAUDE.md 2>/dev/null || true

Repository: glific/docs

Length of output: 12340


Restrict authenticated screenshot routes to documented safe routes.

The workflow labels issue-controlled input as the doc prompt, and the screenshot script captures every SCREENSHOT:<slug>:<route> placeholder using staging login credentials before opening a PR. Add a documented allowlist in .github/scripts/docs-agent-prompt.md, validate each route against it before page.goto() in .github/scripts/take-screenshots.mjs, reject unapproved routes, and use a staging account containing only scrubbed demonstration data.

🧰 Tools
🪛 markdownlint-cli2 (0.23.2)

[warning] 29-29: Images should have alternate text (alt text)

(MD045, no-alt-text)

📍 Affects 2 files
  • .github/scripts/docs-agent-prompt.md#L25-L35 (this comment)
  • .github/scripts/take-screenshots.mjs#L96-L103
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/docs-agent-prompt.md around lines 25 - 35, Add a documented
allowlist of safe screenshot route patterns in the prompt section around the
SCREENSHOT placeholder instructions in .github/scripts/docs-agent-prompt.md. In
.github/scripts/take-screenshots.mjs, validate every extracted route against
that allowlist before page.goto(), reject unapproved routes, and ensure
screenshot authentication uses a staging account containing only scrubbed
demonstration data; update both affected files as specified.


5. If, after investigating, this issue does not actually require a documentation change,
make NO file changes and instead write exactly the word "SKIP" as the first line of
pr-body.md at the repo root, followed by a one-sentence explanation on the next line.

6. Otherwise, write a concise PR description (2-4 sentences: what changed and why,
referencing the issue) to pr-body.md at the repo root. Body text only, no title.

Constraints:
- Only create or edit files under docs/, plus the single file pr-body.md at the repo
root. Do not touch workflow files, package.json, static/img/generated/, or anything
under repos/.
- Do not invent product behavior you can't find evidence for in the two repos; if
uncertain, note the uncertainty in the doc text rather than guessing confidently.
126 changes: 126 additions & 0 deletions .github/scripts/take-screenshots.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Scans the docs/ files Claude just edited for `SCREENSHOT:<slug>:<route>` placeholders,
// captures each route from the staging Glific instance, and rewrites the placeholder
// with a real image path. No AI involved here — purely mechanical, run after the
// Claude authoring step in .github/workflows/auto-docs.yml.
//
// Logs in once and reuses that single browser session for every screenshot in the run
// (Glific auth is phone number + password — see src/containers/Auth/Login/Login.tsx
// in glific-frontend: the field names are "phoneNumber" and "password", and the submit
// button is data-testid="SubmitButton"; login finishes with a hard page redirect away
// from /login rather than client-side routing).

import { execSync } from "node:child_process";
import { chromium } from "playwright";
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { dirname, relative } from "node:path";

const STAGING_URL = requireEnv("GLIFIC_STAGING_URL").replace(/\/+$/, "");
const PHONE = requireEnv("GLIFIC_STAGING_PHONE");
const PASSWORD = requireEnv("GLIFIC_STAGING_PASSWORD");
const ISSUE_NUMBER = requireEnv("ISSUE_NUMBER");

const PLACEHOLDER_RE = /!\[\]\(SCREENSHOT:([a-z0-9-]+):([^)]+)\)/g;

function requireEnv(name) {
const value = process.env[name];
if (!value) {
console.error(`Missing required env var ${name}`);
process.exit(1);
}
return value;
}

function changedDocFiles() {
const output = execSync("git status --porcelain -- docs", {
encoding: "utf8",
});
return output
.split("\n")
.map((line) => line.slice(3).trim())
.filter((path) => path.endsWith(".md") || path.endsWith(".mdx"));
}

function findPlaceholders(files) {
const found = [];
for (const file of files) {
const content = readFileSync(file, "utf8");
for (const match of content.matchAll(PLACEHOLDER_RE)) {
found.push({ file, full: match[0], slug: match[1], route: match[2] });
}
}
return found;
}

function relativeImagePath(docFile, imagePath) {
const rel = relative(dirname(docFile), imagePath);
return rel.startsWith(".") ? rel : `./${rel}`;
Comment on lines +54 to +56

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Write the public Docusaurus image path.

relativeImagePath() produces paths such as ../../static/img/generated/.... Docusaurus serves static/ at the site root, so these links resolve to a nonexistent /static/... URL in published docs. Generate /img/generated/${ISSUE_NUMBER}/${slug}.png directly.

Proposed fix
-function relativeImagePath(docFile, imagePath) {
-  const rel = relative(dirname(docFile), imagePath);
-  return rel.startsWith(".") ? rel : `./${rel}`;
-}
-
 ...
-      const markdown = `![${slug}](${relativeImagePath(file, outPath)})`;
+      const markdown = `![${slug}](/img/generated/${ISSUE_NUMBER}/${slug}.png)`;

Also applies to: 105-105

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/take-screenshots.mjs around lines 54 - 56, Replace the
relative-path logic in relativeImagePath with the public Docusaurus URL format,
returning /img/generated/${ISSUE_NUMBER}/${slug}.png using the image’s issue
number and slug instead of traversing from docFile. Ensure generated
documentation links resolve from the site root and preserve the expected PNG
filename.

}

async function login(page) {
await page.goto(`${STAGING_URL}/login`, { waitUntil: "networkidle" });
await page.fill('input[name="phoneNumber"]', PHONE);
await page.fill('input[name="password"]', PASSWORD);
try {
await Promise.all([
page.waitForURL((url) => !url.pathname.includes("/login"), {
timeout: 20_000,
}),
page.click('[data-testid="SubmitButton"]'),
]);
} catch (err) {
throw new Error(
`Login to staging Glific instance (${STAGING_URL}) did not leave /login within 20s — check GLIFIC_STAGING_PHONE/GLIFIC_STAGING_PASSWORD. Underlying error: ${err.message}`
);
}
await page.waitForLoadState("networkidle");
}

async function main() {
const files = changedDocFiles();
const placeholders = findPlaceholders(files);

if (placeholders.length === 0) {
console.log("No SCREENSHOT: placeholders found, nothing to capture.");
return;
}

const browser = await chromium.launch();
const page = await browser.newPage({
viewport: { width: 1440, height: 900 },
});

try {
await login(page); // one session, reused for every capture below

const replacements = new Map(); // file -> [{full, markdown}]
for (const { file, full, slug, route } of placeholders) {
const outDir = `static/img/generated/${ISSUE_NUMBER}`;
const outPath = `${outDir}/${slug}.png`;
mkdirSync(outDir, { recursive: true });

console.log(`Capturing ${route} -> ${outPath}`);
await page.goto(`${STAGING_URL}${route}`, { waitUntil: "networkidle" });
await page.screenshot({ path: outPath });

const markdown = `![${slug}](${relativeImagePath(file, outPath)})`;
const list = replacements.get(file) ?? [];
list.push({ full, markdown });
replacements.set(file, list);
}

for (const [file, list] of replacements) {
let content = readFileSync(file, "utf8");
for (const { full, markdown } of list) {
content = content.split(full).join(markdown);
}
writeFileSync(file, content);
}
} finally {
await browser.close();
}
}

main().catch((err) => {
console.error(err);
process.exit(1);
});
160 changes: 160 additions & 0 deletions .github/workflows/auto-docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
name: Auto Docs (label-triggered)

# Fires when an issue is labeled "auto-docs". Investigates the ticket against
# the glific and glific-frontend repos, updates the relevant doc page(s) with
# Claude, captures a fresh screenshot of the real app for any page that needs
# one, and opens a PR for human review. Never auto-merges.

on:
issues:
types: [labeled]

permissions:
contents: write
pull-requests: write
issues: write
Comment on lines +12 to +15

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Prevent Claude from reading a write-capable Git credential.

actions/checkout persists credentials by default. The issue-controlled agent can read .git/config because it has Read access. A prompt-injected issue can cause the agent to write that credential into an allowed documentation file, and the workflow then publishes it in a PR.

Set persist-credentials: false on all checkouts. Inject a limited push credential only after the Claude step. If the Claude action can still access a write token, split authoring and PR creation into separate jobs and give the authoring job read-only permissions.

Also applies to: 22-37

🧰 Tools
🪛 zizmor (1.29.0)

[error] 13-13: overly broad permissions (excessive-permissions): contents: write is overly broad at the workflow level

(excessive-permissions)


[error] 14-14: overly broad permissions (excessive-permissions): pull-requests: write is overly broad at the workflow level

(excessive-permissions)


[error] 15-15: overly broad permissions (excessive-permissions): issues: write is overly broad at the workflow level

(excessive-permissions)


[warning] 13-13: permissions without explanatory comments (undocumented-permissions): needs an explanatory comment

(undocumented-permissions)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/auto-docs.yml around lines 12 - 15, Update every
actions/checkout invocation in the workflow to set persist-credentials: false,
including the checkout covered by the later workflow section. Ensure the Claude
authoring step cannot access any write-capable credential; provide a limited
push credential only after that step, or split authoring and pull-request
creation into separate jobs with read-only permissions for authoring.

Source: Linters/SAST tools


jobs:
auto-docs:
if: github.event.label.name == 'auto-docs' && github.event.issue.pull_request == null
runs-on: ubuntu-latest
Comment on lines +17 to +20

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Serialize and make repeated issue runs idempotent.

A second auto-docs label event for the same issue can overlap the first run or occur after its branch exists. git checkout -b "$branch" then fails, or concurrent pushes conflict. Add issue-scoped workflow concurrency and detect an existing open PR before creating the branch.

Also applies to: 135-142

🧰 Tools
🪛 zizmor (1.29.0)

[info] 18-18: workflow or action definition without a name (anonymous-definition): this job

(anonymous-definition)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/auto-docs.yml around lines 17 - 20, Update the auto-docs
job around its workflow trigger and branch-creation steps to serialize runs per
issue using issue-scoped concurrency, then check for an existing open pull
request for that issue before creating or pushing the branch. Reuse the existing
branch/PR symbols and exit successfully when the documentation PR already
exists, while preserving normal branch creation for new issues.

Source: Linters/SAST tools

steps:
- name: Checkout docs
uses: actions/checkout@v4
Comment on lines +22 to +23

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow excerpt =="
cat -n .github/workflows/auto-docs.yml | sed -n '1,130p'

echo
echo "== git diff stat/name-status (if available) =="
git diff --stat HEAD~1..HEAD 2>/dev/null || true
git diff --numstat HEAD~1..HEAD 2>/dev/null || true

echo
echo "== references to checkout/setup-node/claude-code-action in workflow =="
rg -n "actions/checkout|anthropics/claude-code-action|actions/setup-node|uses:" .github/workflows/auto-docs.yml

Repository: glific/docs

Length of output: 6560


Pin GitHub Actions to commit SHAs.

This workflow runs with write permissions and passes ANTHROPIC_API_KEY, GITHUB_TOKEN, and staging credentials to the code fetched by these actions. Pin actions/checkout, anthropics/claude-code-action, and actions/setup-node to reviewed commit SHAs instead of mutable tags.

🧰 Tools
🪛 zizmor (1.29.0)

[warning] 22-23: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 23-23: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/auto-docs.yml around lines 22 - 23, Update the workflow
steps using actions/checkout, anthropics/claude-code-action, and
actions/setup-node to reference reviewed immutable commit SHAs instead of
mutable version tags, preserving each action’s existing configuration and
behavior.

Source: Linters/SAST tools


- name: Checkout glific (backend, read-only context)
uses: actions/checkout@v4
with:
repository: glific/glific
path: repos/glific
fetch-depth: 1

- name: Checkout glific-frontend (read-only context)
uses: actions/checkout@v4
with:
repository: glific/glific-frontend
path: repos/glific-frontend
fetch-depth: 1

- name: Build prompt
id: build_prompt
env:
ISSUE_TITLE: ${{ github.event.issue.title }}
ISSUE_BODY: ${{ github.event.issue.body }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
run: |
envsubst < .github/scripts/docs-agent-prompt.md > /tmp/prompt.md
{
echo 'prompt<<EOF_PROMPT_9f3a1c'
cat /tmp/prompt.md
echo 'EOF_PROMPT_9f3a1c'
} >> "$GITHUB_OUTPUT"

- name: Run Claude doc-authoring step
uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: ${{ steps.build_prompt.outputs.prompt }}
claude_args: |
--max-turns 15
--allowedTools "Read,Glob,Grep,Edit,Write"

- name: Remove cloned context repos (never committed, not screenshotted)
run: rm -rf repos

- name: Guardrail - confirm Claude only touched docs/ and pr-body.md
id: guardrail
run: |
# cut -c4- (not awk) because doc folder names contain spaces, e.g.
# "docs/3. Product Features/..." — awk would truncate at the first space.
changed=$(git status --porcelain | cut -c4-)
bad=0
while IFS= read -r f; do
[ -z "$f" ] && continue
case "$f" in
*' -> '*) f="${f#*-> }" ;; # renames: check the destination path
esac
case "$f" in
docs/*|pr-body.md) ;;
*)
echo "::error::Unexpected file changed outside docs/: $f"
bad=1
;;
esac
done <<< "$changed"
Comment on lines +70 to +84

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Parse Git status paths as NUL-delimited records.

git status --porcelain C-quotes paths that contain spaces. Both sites treat the quoted output as a literal path. This rejects valid documentation paths such as docs/3. Product Features/... in the guardrail and causes readFileSync() to read a nonexistent quoted path.

  • .github/workflows/auto-docs.yml#L70-L84: use git status --porcelain=v1 -z and a NUL-safe loop. Handle both paths for rename records.
  • .github/scripts/take-screenshots.mjs#L33-L41: use NUL-delimited porcelain output and preserve each path without trimming or slicing quoted text.
📍 Affects 2 files
  • .github/workflows/auto-docs.yml#L70-L84 (this comment)
  • .github/scripts/take-screenshots.mjs#L33-L41
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/auto-docs.yml around lines 70 - 84, Parse Git status
output as NUL-delimited records in both affected sites: update
.github/workflows/auto-docs.yml lines 70-84 to use porcelain v1 with -z, a
NUL-safe loop, and inspect both source and destination paths for renames; update
.github/scripts/take-screenshots.mjs lines 33-41 to use the same NUL-delimited
output and preserve each path exactly without trimming or slicing quoted text.

if [ "$bad" = "1" ]; then
echo "Claude's changes touched files outside the allowed scope. Aborting without committing." >&2
exit 1
fi

if [ -f pr-body.md ] && [ "$(head -n1 pr-body.md)" = "SKIP" ]; then
echo "skip=true" >> "$GITHUB_OUTPUT"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi

- name: Comment when skipped
if: steps.guardrail.outputs.skip == 'true'
run: |
reason=$(tail -n +2 pr-body.md)
gh issue comment "${{ github.event.issue.number }}" --body "Auto-docs pipeline ran but made no changes: ${reason:-no docs update was needed for this ticket.}"
rm -f pr-body.md
env:
GITHUB_TOKEN: ${{ github.token }}

- name: Set up Node
if: steps.guardrail.outputs.skip == 'false'
uses: actions/setup-node@v3
with:
node-version: 18
cache: yarn
Comment on lines +105 to +110

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

npx --yes actionlint .github/workflows/auto-docs.yml

Repository: glific/docs

Length of output: 626


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow files =="
git ls-files .github/workflows || true

echo "== target workflow relevant lines =="
if [ -f .github/workflows/auto-docs.yml ]; then
  nl -ba .github/workflows/auto-docs.yml | sed -n '90,120p'
fi

echo "== all setup-node actions in workflows =="
rg -n 'uses:\s*actions/setup-node@' .github/workflows || true

echo "== available binaries/tools =="
command -v actionlint || true
command -v npx || true
npm config get registry || true

Repository: glific/docs

Length of output: 412


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== target workflow lines with line numbers =="
python3 - <<'PY'
from pathlib import Path
path = Path('.github/workflows/auto-docs.yml')
for idx, line in enumerate(path.read_text().splitlines(), 1):
    if 90 <= idx <= 120:
        print(f"{idx:4}: {line}")
PY

echo "== all setup-node action pins =="
python3 - <<'PY'
from pathlib import Path
for path in Path('.github/workflows').glob('*'):
    text = path.read_text(errors='replace')
    for line in text.splitlines():
        if 'uses:' in line and 'actions/setup-node@' in line:
            print(f"{path}:{line.strip()}")
PY

echo "== actionlint availability =="
command -v actionlint || true
command -v npx || true

echo "== actionlint from npm if installed globally in prefix =="
for bin in /usr/local/opt/ruby/bin/actionlint \
             /usr/local/bin/actionlint \
             /usr/bin/actionlint \
             ~/.npm-g-global/bin/actionlint; do
  [ -x "$bin" ] && echo "$bin exists and is executable" || true
done

Repository: glific/docs

Length of output: 1889


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== actionlint workflow report =="
/usr/bin/actionlint .github/workflows/auto-docs.yml || true

echo "== actionlint version =="
/usr/bin/actionlint --version || true

echo "== actionlint setup-node failure scan across workflows =="
/usr/bin/actionlint .github/workflows || true

Repository: glific/docs

Length of output: 702


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== all actionlint action runner warnings =="
python3 - <<'PY'
import subprocess, pathlib
for path in pathlib.Path('.github/workflows').glob('*'):
    result = subprocess.run(['/usr/bin/actionlint', str(path)], text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    if result.returncode != 0:
        for line in (result.stdout + result.stderr).splitlines():
            if 'runner' in line and 'too old' in line:
                print(line)
PY

echo "== GitHub actions/setup-node v4 branch existence =="
curl -fsSLI -o /dev/null https://github.com/actions/setup-node/tree/v4 || true

Repository: glific/docs

Length of output: 1841


🌐 Web query:

actions/setup-node v4 branch GitHub releases

💡 Result:

The actions/setup-node repository manages its releases using Git tags, and v4 is a specific major version series within that history [1][2]. Key details regarding v4 and the repository's release structure: 1. Versioning: The action uses Semantic Versioning (SemVer) with tags like v4.0.0, v4.0.4, and v4.4.0 [3][4][5]. Each major version (e.g., v4, v5, v6, v7) represents a distinct branch or evolution of the action, often involving updates to the underlying Node.js runtime for the action itself [3][6][7][8]. 2. Current Status: As of August 7, 2026, the current major version is v7 [6][8]. The v4 series is no longer the latest [1][6]. 3. GitHub Releases: All releases, including the v4 series, are documented on the official GitHub Releases page for the repository [1]. Each tag corresponds to a specific release state, allowing users to pin their workflows to specific versions (e.g., actions/setup-node@v4 or actions/setup-node@v4.4.0) to ensure stability [1][5][8]. For users looking to reference v4 specifically in their workflows, using the tag actions/setup-node@v4 will generally point to the latest minor/patch release within that major version [1][5]. However, it is recommended to check the releases page for the specific requirements and features associated with newer major versions like v7 [1][6][8].

Citations:


Upgrade actions/setup-node to a supported version.

actionlint reports that actions/setup-node@v3 uses a runner that GitHub Actions no longer supports, so this Node dependency setup can fail before yarn install. Update the pin to actions/setup-node@v4 or a newer supported release and revalidate the workflow.

🧰 Tools
🪛 actionlint (1.7.12)

[error] 107-107: the runner of "actions/setup-node@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

🪛 zizmor (1.29.0)

[error] 107-107: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/auto-docs.yml around lines 105 - 110, Update the
setup-node action version in the “Set up Node” workflow step from v3 to
actions/setup-node@v4 or another newer supported release, while preserving its
existing condition and yarn cache configuration.

Source: Linters/SAST tools


- name: Install dependencies
if: steps.guardrail.outputs.skip == 'false'
run: yarn install --frozen-lockfile

- name: Install Playwright browser
if: steps.guardrail.outputs.skip == 'false'
run: npx playwright install --with-deps chromium

- name: Take screenshots and rewrite placeholders
if: steps.guardrail.outputs.skip == 'false'
env:
GLIFIC_STAGING_URL: ${{ secrets.GLIFIC_STAGING_URL }}
GLIFIC_STAGING_PHONE: ${{ secrets.GLIFIC_STAGING_PHONE }}
GLIFIC_STAGING_PASSWORD: ${{ secrets.GLIFIC_STAGING_PASSWORD }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
run: node .github/scripts/take-screenshots.mjs

- name: Open PR
if: steps.guardrail.outputs.skip == 'false'
env:
GITHUB_TOKEN: ${{ github.token }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
run: |
branch="docs/auto-${ISSUE_NUMBER}"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git checkout -b "$branch"
git add docs
[ -d static/img/generated ] && git add static/img/generated
git commit -m "docs: auto-update from issue #${ISSUE_NUMBER}"
git push -u origin "$branch"

{
cat pr-body.md
echo ""
echo "Closes #${ISSUE_NUMBER}"
echo ""
echo "_Opened automatically by the auto-docs pipeline. Please review before merging._"
} > /tmp/pr-body-final.md
rm -f pr-body.md

gh pr create \
--title "docs: update from issue #${ISSUE_NUMBER}" \
--body-file /tmp/pr-body-final.md \
--label auto-docs \
--base main \
--head "$branch"

gh issue comment "$ISSUE_NUMBER" --body "Opened a docs PR for this: $(gh pr view "$branch" --json url -q .url)"
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Dependencies
/node_modules

# Sibling product repos cloned by the auto-docs workflow for context only
/repos

.env

# Production
Expand Down
Loading
Loading