Skip to content
138 changes: 138 additions & 0 deletions .github/actions/fix-ubuntu-ca-roots/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Shared CA fix for the GitHub Actions workflows of all okTurtles repos. They
# all call this copy, so keep it free of repo-specific content.
#
# The compromised eMudhra intermediates chain to emSign ECC Root CA - G3 and
# to the Comodo AAA root (AAA Certificate Services). Ubuntu's fixed
# ca-certificates package removes both roots, but runner images can predate
# it. This action upgrades the packages and deselects both roots in case the
# upgrade did not. It also deselects emSign ECC Root CA - C3 and emSign Root
# CA - C1. These two are not part of the compromise, but the CA made the
# compromised keys incorrectly, so its other keys can have the same fault,
# and CI does not need an eMudhra root. Then the action rebuilds the bundle
# and fails if one of the four roots is still trusted.
# https://emudhra.we-have-a-problem.com/#linux
#
# Scope: GitHub-hosted Ubuntu runners. The action needs sudo, apt-get, an
# installed libnss3 and a Debian-style /etc/ca-certificates.conf.
#
# Callers must:
# - Run it as the first step of every job that has steps, before checkout
# and any other HTTPS request. Call it as
# okTurtles/libcheloniajs/.github/actions/fix-ubuntu-ca-roots@main, not
# with a ./ path: the runner downloads remote actions in "Set up job",
# before the first step, but a ./ path needs a checkout. This also keeps a
# workflow that checks out untrusted PR code from loading the action from
# that checkout.
# - Set NODE_OPTIONS=--use-openssl-ca in the workflow-level env of every
# workflow that has such a job. Node does not read the system bundle. It
# uses its built-in CA list, and unpatched Node releases, such as the
# runner's own Node for JavaScript actions, still trust the bad chains.
# --use-openssl-ca replaces that list with the fixed system bundle. Do
# not use --use-system-ca: it adds the system roots to the built-in list,
# so the bad roots stay. This action cannot set NODE_OPTIONS for the steps
# after it, because GitHub blocks it in GITHUB_ENV. A reusable
# (workflow_call) workflow must set it itself, because the env of its
# caller does not reach it. A job that only calls a reusable workflow
# cannot have steps, so it needs neither this action nor NODE_OPTIONS.
# https://github.blog/changelog/2023-10-05-github-actions-node_options-is-now-restricted-from-github_env/
# - Pass NODE_OPTIONS (and DENO_TLS_CA_STORE and SSL_CERT_FILE, if Deno or
# uv runs there) through commands that clear the environment, such as
# env -i, bwrap --clearenv and sudo.
# - Point a tool that has its own root list and does not read SSL_CERT_FILE
# at /etc/ssl/certs/ca-certificates.crt. This action does not fix it. For
# example, Python requests uses only certifi, and certifi before 2026.10.18
# still trusts the bad chains: set REQUESTS_CA_BUNDLE in the jobs that run
# it. The action does not set it for every job, because with it requests
# ignores Session.verify = False.
# - If a step runs after a failure (failure(), always(), cancelled()) and
# sends a token or other secret over HTTPS, give this action's step an id
# and add steps.<id>.outcome == 'success' to that step's if. When this
# action fails, the bundle can still trust a removed root.
# - Set sync-nss-db to true only in jobs that run Playwright or Chromium.
#
# env:
# NODE_OPTIONS: --use-openssl-ca
# jobs:
# test:
# runs-on: ubuntu-latest
# steps:
# - uses: okTurtles/libcheloniajs/.github/actions/fix-ubuntu-ca-roots@main
# with:
# sync-nss-db: true # only if the job runs Playwright or Chromium
# - uses: actions/checkout@v4
#
# A change to this action reaches the callers only when it merges to main.
# The CI of the PR that changes it still runs the copy on main, so that CI
# does not test the change.
name: Fix Ubuntu runner CA root bundle
description: On GitHub-hosted Ubuntu runners, upgrade ca-certificates and libnss3, remove emSign ECC Root CA - G3, emSign ECC Root CA - C3, emSign Root CA - C1 and Comodo AAA from the system CA bundle, and make Deno and uv use that bundle.
inputs:
sync-nss-db:
description: Also trust the fixed system bundle and distrust the removed roots in the user's NSS database, which Chromium reads.
default: "false"
runs:
using: composite
steps:
- name: Fix system CA bundle
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y --only-upgrade ca-certificates 'libnss3*'
dpkg -l ca-certificates 'libnss3*' | grep ^ii
sudo sed -i \
-e 's|^mozilla/emSign_ECC_Root_CA_-_G3\.crt$|!&|' \
-e 's|^mozilla/emSign_ECC_Root_CA_-_C3\.crt$|!&|' \
-e 's|^mozilla/emSign_Root_CA_-_C1\.crt$|!&|' \
-e 's|^mozilla/Comodo_AAA_Services_root\.crt$|!&|' \
/etc/ca-certificates.conf
grep -E 'emSign|Comodo_AAA' /etc/ca-certificates.conf || true
sudo update-ca-certificates
subjects=$(openssl crl2pkcs7 -nocrl -certfile /etc/ssl/certs/ca-certificates.crt |
openssl pkcs7 -print_certs -noout)
if grep -E 'emSign ECC Root CA - (G3|C3)|emSign Root CA - C1|AAA Certificate Services' <<<"$subjects"; then
echo "::error::/etc/ssl/certs/ca-certificates.crt still trusts a root that this action removes"
exit 1
fi
# Deno and uv use their bundled Mozilla root lists by default. Deno
# before 2.9.8 and uv before 0.13.4 still trust the bad chains.
# DENO_TLS_CA_STORE=system makes Deno use the fixed system bundle.
# SSL_CERT_FILE makes uv, and httpx in place of certifi, use only that
# file. OpenSSL, Go and Python's ssl module also read SSL_CERT_FILE, but
# on Ubuntu they already use that file. Unlike NODE_OPTIONS, GitHub
# allows both variables in GITHUB_ENV, so the callers do not have to set
# them.
# https://docs.deno.com/runtime/reference/env_variables/
# https://docs.astral.sh/uv/concepts/authentication/certificates/
- name: Make Deno and uv use the system CA bundle
shell: bash
run: |
echo "DENO_TLS_CA_STORE=system" >> "$GITHUB_ENV"
echo "SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt" >> "$GITHUB_ENV"
# Chromium ignores the system CA bundle. It trusts its built-in Chrome
# Root Store plus the user's NSS database, and Playwright's Chromium gets
# no CRLSet updates, so it still accepts the compromised eMudhra chains.
# Trust the fixed system bundle there (newer roots than the built-in
# store) and mark the removed roots as distrusted, which overrides the
# built-in store. The roots come from distrusted-ca-roots.pem because the
# fixed ca-certificates package deletes some of them.
# https://chromium.googlesource.com/chromium/src/+/main/net/data/ssl/chrome_root_store/faq.md
# https://chromium.googlesource.com/chromium/src/+/main/docs/linux/cert_management.md
- name: Sync NSS database with the system bundle
if: inputs.sync-nss-db == 'true'
shell: bash
run: |
sudo apt-get install -y -qq libnss3-tools
nssdb="sql:$HOME/.pki/nssdb"
mkdir -p "$HOME/.pki/nssdb"
[ -f "$HOME/.pki/nssdb/cert9.db" ] || certutil -d "$nssdb" -N --empty-password
nss_import() {
local trust=$1 prefix=$2 bundle=$3 certs
certs=$(mktemp -d)
awk -v dir="$certs" '/-----BEGIN CERTIFICATE-----/ { if (out) close(out); out = sprintf("%s/%03d.pem", dir, ++n) } out { print > out }' "$bundle"
for cert in "$certs"/*.pem; do
certutil -d "$nssdb" -A -t "$trust" -n "$prefix-$(basename "$cert" .pem)" -i "$cert"
done
echo "$prefix: $(certutil -d "$nssdb" -L | grep -c "^$prefix-") certificates"
}
nss_import C,, system-ca /etc/ssl/certs/ca-certificates.crt
nss_import p,p,p distrusted-ca "$GITHUB_ACTION_PATH/distrusted-ca-roots.pem"
84 changes: 84 additions & 0 deletions .github/actions/fix-ubuntu-ca-roots/distrusted-ca-roots.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Roots that action.yml removes from the system bundle and, with
# sync-nss-db, marks as distrusted in the user's NSS database (eMudhra
# compromise, https://emudhra.we-have-a-problem.com/).
# Copied from Ubuntu ca-certificates 20240203.
# SHA-256 fingerprints, in file order:
# AAA Certificate Services D7A7A0FB5D7E2731D771E9484EBCDEF71D5F0C3E0A2948782BC83EE0EA699EF4
# emSign ECC Root CA - C3 BC4D809B15189D78DB3E1D8CF4F9726A795DA1643CA5F1358E1DDB0EDC0D7EB3
# emSign ECC Root CA - G3 86A1ECBA089C4A8D3BBE2734C612BA341D813E043CF9E8A862CD5C57A36BBE6B
# emSign Root CA - C1 125609AA301DA0A249B97A8239CB6A34216F44DCAC9F3954B14292F2E8C8608F
-----BEGIN CERTIFICATE-----
MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb
MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj
YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL
MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM
GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua
BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe
3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4
YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR
rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm
ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU
oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF
MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v
QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t
b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF
AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q
GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz
Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2
G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi
l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3
smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQG
EwJVUzETMBEGA1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMx
IDAeBgNVBAMTF2VtU2lnbiBFQ0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAw
MFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln
biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQDExdlbVNpZ24gRUND
IFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd6bci
MK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4Ojavti
sIGJAnB9SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0O
BBYEFPtaSNCAIEDyqOkAB2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB
Af8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQC02C8Cif22TGK6Q04ThHK1rt0c
3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwUZOR8loMRnLDRWmFLpg9J
0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG
EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo
bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g
RzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJ
TjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s
b2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMw
djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0
WXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xyS
fvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB
zhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq
hkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB
CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD
+JbNR6iC8hZVdyR+EhCVBCyj
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkG
A1UEBhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEg
SW5jMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAw
MFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln
biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNpZ24gUm9v
dCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+upufGZ
BczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZ
HdPIWoU/Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH
3DspVpNqs8FqOp099cGXOFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvH
GPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4VI5b2P/AgNBbeCsbEBEV5f6f9vtKppa+c
xSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleoomslMuoaJuvimUnzYnu3Yy1
aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+XJGFehiq
TbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
BQADggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87
/kOXSTKZEhVb3xEp/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4
kqNPEjE2NuLe/gDEo2APJ62gsIq1NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrG
YQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9wC68AivTxEDkigcxHpvOJpkT
+xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQBmIMMMAVSKeo
WXzhriKi4gp6D/piq1JM4fHfyr6DDUI=
-----END CERTIFICATE-----
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,17 @@ on:
pull_request:
branches: [ "main" ]

env:
# Node must use the system CA bundle that fix-ubuntu-ca-roots fixes. See
# .github/actions/fix-ubuntu-ca-roots/action.yml.
NODE_OPTIONS: --use-openssl-ca

jobs:
build:
runs-on: ubuntu-latest
environment: CI
steps:
- uses: okTurtles/libcheloniajs/.github/actions/fix-ubuntu-ca-roots@main
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
Expand Down
45 changes: 33 additions & 12 deletions .github/workflows/pull-review-crush.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ on:
description: API key for the kimi provider.
required: false

env:
# Node must use the system CA bundle that fix-ubuntu-ca-roots fixes. See
# .github/actions/fix-ubuntu-ca-roots/action.yml.
NODE_OPTIONS: --use-openssl-ca

jobs:
review:
name: Automated PR Review
Expand Down Expand Up @@ -101,6 +106,9 @@ jobs:
KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }}

steps:
- uses: okTurtles/libcheloniajs/.github/actions/fix-ubuntu-ca-roots@main
id: ca-roots

- name: Update comment with status
env:
COMMENT_BODY: ${{ github.event.comment.body }}
Expand Down Expand Up @@ -512,11 +520,12 @@ jobs:
#
# The skills CLI is a third-party npx package that needs no secrets (the
# skill repo is public, so an anonymous clone works). `env -i` passes it
# only PATH, HOME and DISABLE_TELEMETRY, so no job env reaches it. The
# step env also blanks the secrets in this step's own shell, because a
# child process can read its parent's environment from /proc. This
# stops packages that harvest environment variables, but it is not a
# sandbox: the package still runs unsandboxed as the runner user.
# only PATH, HOME, DISABLE_TELEMETRY and NODE_OPTIONS, so no other job
# env reaches it. The step env also blanks the secrets in this step's
# own shell, because a child process can read its parent's environment
# from /proc. This stops packages that harvest environment variables,
# but it is not a sandbox: the package still runs unsandboxed as the
# runner user.
- name: Install code-review skill
env:
GH_TOKEN: ''
Expand All @@ -526,7 +535,7 @@ jobs:
PROVIDER_API_KEY: ''
run: |
cd "$HOME"
env -i PATH="$PATH" HOME="$HOME" DISABLE_TELEMETRY=1 \
env -i PATH="$PATH" HOME="$HOME" DISABLE_TELEMETRY=1 NODE_OPTIONS="$NODE_OPTIONS" \
npx --yes skills add taoeffect/code-review --skill code-review -g -a crush --copy -y

SKILL_FILE="$HOME/.config/crush/skills/code-review/SKILL.md"
Expand Down Expand Up @@ -646,6 +655,7 @@ jobs:
--setenv PATH "$PATH"
--setenv HOME "$HOME"
--setenv TERM dumb
--setenv NODE_OPTIONS "$NODE_OPTIONS"
--setenv OPENROUTER_API_KEY "$OPENROUTER_API_KEY"
--setenv ZAI_API_KEY "$ZAI_API_KEY"
--setenv KIMI_API_KEY "$KIMI_API_KEY"
Expand Down Expand Up @@ -759,23 +769,30 @@ jobs:
--repo "${{ github.repository }}" \
--body-file /tmp/review_comment.txt

# Skipped when fix-ubuntu-ca-roots fails: the CA bundle can then still
# trust a removed root, and gh would send GH_TOKEN through it. That
# failure shows only in the Actions log. A later step can fail before
# "Get PR number" and "Extract review command" run, so this step takes
# the PR number from the event and gives REVIEW_COMMAND a default.
- name: Post failure comment
if: failure() && steps.pr-info.outputs.number
if: failure() && steps.ca-roots.outcome == 'success'
run: |
gh pr comment "${{ steps.pr-info.outputs.number }}" \
gh pr comment "${{ github.event.issue.number }}" \
--repo "${{ github.repository }}" \
--body "## Advanced AI Review

- Type: Error
- Model: ${REVIEW_MODEL}

The review workflow failed. Check the [workflow run](${RUN_URL}) for details. Comment \`${REVIEW_COMMAND}\` to re-run."
The review workflow failed. Check the [workflow run](${RUN_URL}) for details. Comment \`${REVIEW_COMMAND:-/review}\` to re-run."

# Runs when the job is cancelled. The elapsed-time check distinguishes
# the job timeout (timeout-minutes above) from a concurrency
# cancel-in-progress cancellation, which must NOT add the note.
# cancel-in-progress cancellation, which must NOT add the note. Skipped
# when fix-ubuntu-ca-roots did not succeed, for the reason given at
# "Post failure comment".
- name: Report timeout on trigger comment
if: cancelled()
if: cancelled() && steps.ca-roots.outcome == 'success'
env:
COMMENT_ID: ${{ github.event.comment.id }}
TIMEOUT_MINUTES: ${{ inputs.timeout-minutes }}
Expand All @@ -787,7 +804,11 @@ jobs:
exit 0
fi
ELAPSED=$(( $(date +%s) - JOB_STARTED_AT ))
if [ "$ELAPSED" -lt $(( TIMEOUT_SECONDS - 60 )) ]; then
# timeout-minutes counts from the job start, but JOB_STARTED_AT is set after
# "Set up job" and fix-ubuntu-ca-roots (apt-get update and upgrade), so the
# slack must cover both. Cost: a concurrency cancellation in the last
# 5 minutes is reported as a timeout.
if [ "$ELAPSED" -lt $(( TIMEOUT_SECONDS - 300 )) ]; then
echo "Cancelled after ${ELAPSED}s (< timeout) — likely concurrency cancellation; skipping"
exit 0
fi
Expand Down
23 changes: 23 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,29 @@ Dual ESM/CJS output with TypeScript declarations:

**Note**: CJS build excludes test files via `tsconfig.cjs.json`.

## CI

GitHub Actions files in `.github/`:

| Path | Purpose |
|---|---|
| `workflows/ci.yml` | Runs `npm ci` and `npm test` on pushes and PRs to `main` |
| `workflows/pr-review.yml` | Runs `pull-review-crush.yml` when a member comments `/review` on a PR |
| `workflows/pull-review-crush.yml` | Reusable AI PR review workflow. Other okTurtles repos call it through `@main` |
| `actions/fix-ubuntu-ca-roots/` | Shared action that removes distrusted CA roots on the runner. All okTurtles repos call it through `@main` |

Rules:

- Every job that has `steps:` runs
`okTurtles/libcheloniajs/.github/actions/fix-ubuntu-ca-roots@main` as its first step.
- Every workflow with such a job sets `NODE_OPTIONS: --use-openssl-ca` in its workflow-level
`env:`. A job that only calls a reusable workflow (`pr-review.yml`) needs neither.
- Other repos use the action and `pull-review-crush.yml` from `main`. A change to them affects
those repos when it merges, and the PR's own CI still runs the copy on `main`.

The header of `.github/actions/fix-ubuntu-ca-roots/action.yml` gives the reasons and the full
rules for callers.

## Key Dependencies

| Package | Purpose |
Expand Down
Loading