Skip to content

ci: automated updates#126

Merged
wolf31o2 merged 1 commit intomainfrom
ci/automated-updates
Feb 28, 2026
Merged

ci: automated updates#126
wolf31o2 merged 1 commit intomainfrom
ci/automated-updates

Conversation

@wolf31o2
Copy link
Member

@wolf31o2 wolf31o2 commented Feb 18, 2026


Summary by cubic

Automates container image version maintenance with a weekly GitHub Action that detects upstream updates and opens PRs to refresh docker-compose defaults. Adds simple scripts and Makefile targets for local checks, validation, and manual bumps.

  • New Features
    • Scheduled workflow (weekly and manual) scans GHCR/Docker Hub via scripts/upstream-versions.json, verifies tags/images, updates docker-compose defaults, pushes a branch, and opens a PR per package.
    • Scripts: check-versions.sh (DRY_RUN), add-version.sh (single package bump), validate-versions.sh (default validation), plus Makefile targets and README usage.

Written for commit 37e562e. Summary will update on new commits.

Summary by CodeRabbit

Release Notes

  • New Features

    • Automated weekly checks for upstream container image updates with automatic PR creation for version changes.
    • Version validation tool to ensure configured packages have proper default versions.
    • Version update utility for container services.
  • Documentation

    • Added version maintenance workflow documentation and usage examples.

@wolf31o2 wolf31o2 requested review from a team as code owners February 18, 2026 19:32
@coderabbitai
Copy link

coderabbitai bot commented Feb 18, 2026

📝 Walkthrough

Walkthrough

This change introduces a version management system for monitoring and updating upstream container image versions. It adds a GitHub Actions workflow that runs on a weekly schedule to check for new versions across configured packages, compare them with local defaults in docker-compose.yaml, and automatically create pull requests with updates. Supporting infrastructure includes three Bash scripts (check-versions.sh, add-version.sh, validate-versions.sh), a JSON configuration file mapping services to their image sources and version patterns, three new Makefile targets, and documentation in the README describing the version maintenance workflow.

🚥 Pre-merge checks | ✅ 1 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'ci: automated updates' is vague and generic. While it relates to CI/CD changes, it doesn't clearly convey the specific purpose of adding version management automation scripts and workflows. Consider a more descriptive title such as 'ci: add automated upstream version checking workflow' that clearly indicates the main feature being introduced.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ci/automated-updates

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (2)
scripts/add-version.sh (1)

80-80: Minor: new_version in re.sub replacement string is not escaped.

If new_version were to contain backslash characters (e.g., in an unusual tag), re.sub would interpret \1, \2, etc. as backreferences. Extremely unlikely for version strings, but re.escape isn't the right fix for replacement strings — use new_version.replace('\\', '\\\\') if you want to be defensive.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/add-version.sh` at line 80, The replacement passed to pattern.sub
uses new_version directly so backslashes in new_version could be interpreted as
regex backreferences; update the call that assigns updated (the pattern.sub(...,
new_version ...)) to escape backslashes in new_version before passing it to
pattern.sub (e.g., replace every '\' with '\\') so the replacement string is
treated literally; locate the pattern.sub usage that assigns variable updated
and apply new_version.replace('\\','\\\\') (or equivalent) to the replacement
argument.
.github/workflows/check-versions.yml (1)

17-19: fetch-depth: 0 is unnecessary — consider fetch-depth: 1.

The workflow creates new branches and pushes them; it doesn't need full history. A shallow clone (fetch-depth: 1) would speed up the checkout step. You only need enough history to create a branch off main.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/check-versions.yml around lines 17 - 19, The workflow
currently uses actions/checkout with fetch-depth: 0 which does a full clone;
update the checkout step
(actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd) to use fetch-depth:
1 instead of 0 to perform a shallow clone (enough history to branch from main),
keeping the with: fetch-depth key but changing its value to 1 to speed up the
checkout.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/check-versions.yml:
- Line 161: The regex assigned to the variable pattern is over-escaped and will
not match strings like ${VAR:-value}; update the re.compile call (the pattern
variable) to use single backslashes in the raw string prefixes (e.g. r"\$\{" and
r"\}") instead of double-escaped backslashes so it builds a regex that matches
"${compose_var:-...}" correctly; specifically, change the concatenation around
re.escape(compose_var) so the prefix is r"\$\{" and the suffix is r":-([^}]+)\}"
(keep using re.escape(compose_var) unchanged) to restore correct matching.
- Around line 195-206: The script is creating feature branches from the current
(possibly dirty) HEAD via git checkout -b "$BRANCH_NAME" after running
./scripts/add-version.sh, which can cause branches to stack unintended changes;
before creating each branch (before git checkout -b "$BRANCH_NAME") ensure you
explicitly return to a clean main by running git checkout main and then git
fetch origin && git reset --hard origin/main (or equivalent) so the new branch
starts from the remote/main tip; apply this change around the code that calls
./scripts/add-version.sh, git add "$COMPOSE_FILE", git commit -m "chore:
${PACKAGE} ${UPSTREAM_VERSION}", and git push -u origin "$BRANCH_NAME" to
guarantee BRANCH_NAME is created cleanly and unrelated COMPOSE_FILE changes are
not carried forward.

In `@scripts/add-version.sh`:
- Around line 64-85: The heredoc invocation for the Python block is incorrect:
the arguments "$COMPOSE_FILE" "$COMPOSE_VAR" "$NEW_VERSION" are placed after the
PY terminator instead of on the python3 command line; move those three arguments
to the python3 invocation so the inline script receives them (i.e., call python3
- <<'PY' with the arguments immediately following python3, ensuring the script
uses sys.argv[1..3] for compose_file, compose_var, new_version), leaving the
rest of the block (pattern, updated, file writes) unchanged.
- Around line 35-52: The heredoc currently runs python3 without arguments so
sys.argv is empty; change the invocation to pass the two args to python3 before
the here-doc (e.g. OLD_VERSION=$(python3 - "$COMPOSE_FILE" "$COMPOSE_VAR" <<'PY'
... PY "$COMPOSE_FILE" "$COMPOSE_VAR" || echo "") ), or equivalently place the
"$COMPOSE_FILE" "$COMPOSE_VAR" immediately after python3 and before the <<'PY'
so that the python snippet (which uses sys.argv[1] and sys.argv[2]) receives the
expected arguments; keep the internal code using sys.argv[1]/sys.argv[2]
unchanged.

In `@scripts/check-versions.sh`:
- Around line 128-145: The heredoc feeding the inline Python to python3 is
terminated before the "$COMPOSE_FILE" "$COMPOSE_VAR" arguments are passed, so
LOCAL_VERSION is always empty; move the arguments into the heredoc invocation so
python3 receives sys.argv (i.e., ensure the python3 <<'PY' block is followed
immediately by the PY terminator and then call python3 with "$COMPOSE_FILE"
"$COMPOSE_VAR" inside the same command), or alternatively change to a
single-line python3 -c invocation that accepts "$COMPOSE_FILE" "$COMPOSE_VAR";
update the LOCAL_VERSION assignment to run python3 with the inline script
(reading compose_file and compose_var from sys.argv) so pattern.search and
match.group(1) execute correctly and the command returns the version.
- Around line 66-111: The python heredoc in the UPSTREAM_TAG assignment isn't
receiving the shell arguments because "$DOCKER_IMAGE" "$TAG_REGEX" are placed
after the heredoc terminator; move the arguments into the python3 command
invocation so they are passed as sys.argv (e.g., call python3 - "$DOCKER_IMAGE"
"$TAG_REGEX" <<'PY' ... PY), leaving the script body (including the normalize
function) unchanged and ensuring sys.argv[1] and sys.argv[2] map to DOCKER_IMAGE
and TAG_REGEX.

In `@scripts/validate-versions.sh`:
- Around line 41-58: The heredoc Python snippet that computes DEFAULT_VERSION is
currently invoked without passing "$COMPOSE_FILE" and "$COMPOSE_VAR" to python3
(so sys.argv[1]/[2] will be missing); fix by supplying those positional args on
the python3 command line (i.e., pass "$COMPOSE_FILE" "$COMPOSE_VAR" immediately
after the python3 invocation and before the <<'PY' heredoc token) for
DEFAULT_VERSION and apply the same change to the equivalent heredoc calls in
scripts/add-version.sh and scripts/check-versions.sh so the Python code can
safely access sys.argv[1] and sys.argv[2].
- Around line 34-38: The PATTERN assignment uses double quotes which cause
unintended parameter expansion; update the PATTERN construction so the literal
characters "\${" and ":-" are not expanded (e.g., build PATTERN using single
quotes for the fixed pieces or concatenate quoted literals and the variable: use
PATTERN='\\${'"${COMPOSE_VAR}"':-' or switch to a literal search like grep -F --
"$PATTERN" "$COMPOSE_FILE") and ensure the subsequent grep uses the appropriate
option (or proper escaping) so grep searches for the literal string; adjust
references to PATTERN, COMPOSE_VAR, COMPOSE_FILE, and the grep invocation
accordingly.

---

Nitpick comments:
In @.github/workflows/check-versions.yml:
- Around line 17-19: The workflow currently uses actions/checkout with
fetch-depth: 0 which does a full clone; update the checkout step
(actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd) to use fetch-depth:
1 instead of 0 to perform a shallow clone (enough history to branch from main),
keeping the with: fetch-depth key but changing its value to 1 to speed up the
checkout.

In `@scripts/add-version.sh`:
- Line 80: The replacement passed to pattern.sub uses new_version directly so
backslashes in new_version could be interpreted as regex backreferences; update
the call that assigns updated (the pattern.sub(..., new_version ...)) to escape
backslashes in new_version before passing it to pattern.sub (e.g., replace every
'\' with '\\') so the replacement string is treated literally; locate the
pattern.sub usage that assigns variable updated and apply
new_version.replace('\\','\\\\') (or equivalent) to the replacement argument.

compose_file = sys.argv[1]
compose_var = sys.argv[2]

pattern = re.compile(r"\\$\\{" + re.escape(compose_var) + r":-([^}]+)\\}")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Broken regex — extra backslash escaping compared to the standalone scripts.

pattern = re.compile(r"\\$\\{" + re.escape(compose_var) + r":-([^}]+)\\}")

In a Python raw string, \\ is literal \\ (two chars). The regex \\$\\{ means: match literal \, then end-of-string $, then literal \, then { — this will never match ${VAR:-...}.

The standalone scripts correctly use r"\$\{" (single backslash escaping $ and {). This line should match:

-pattern = re.compile(r"\\$\\{" + re.escape(compose_var) + r":-([^}]+)\\}")
+pattern = re.compile(r"\$\{" + re.escape(compose_var) + r":-([^}]+)\}")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/check-versions.yml at line 161, The regex assigned to the
variable pattern is over-escaped and will not match strings like ${VAR:-value};
update the re.compile call (the pattern variable) to use single backslashes in
the raw string prefixes (e.g. r"\$\{" and r"\}") instead of double-escaped
backslashes so it builds a regex that matches "${compose_var:-...}" correctly;
specifically, change the concatenation around re.escape(compose_var) so the
prefix is r"\$\{" and the suffix is r":-([^}]+)\}" (keep using
re.escape(compose_var) unchanged) to restore correct matching.

Comment on lines +195 to +206
if ! ./scripts/add-version.sh "$PACKAGE" "$UPSTREAM_VERSION"; then
echo " WARNING: Failed to update $COMPOSE_FILE for $PACKAGE, skipping"
git checkout -- "$COMPOSE_FILE" || true
continue
fi

git checkout -b "$BRANCH_NAME"
git add "$COMPOSE_FILE"
git commit -m "chore: ${PACKAGE} ${UPSTREAM_VERSION}"

echo " Pushing branch..."
git push -u origin "$BRANCH_NAME"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Branch creation happens on a dirty working tree without resetting to main first.

git checkout -b "$BRANCH_NAME" (Line 201) creates the branch from whatever the current HEAD is. After the first package update, the working tree has uncommitted changes from add-version.sh. While git add and git commit follow, if a previous iteration's git checkout main (Line 225) fails or is skipped, subsequent packages would branch off a non-main commit, stacking unrelated changes.

Consider adding an explicit git checkout main and git reset --hard origin/main before creating each new branch to ensure a clean starting point.

Proposed fix
+            git checkout main
+            git pull origin main
+
             git checkout -b "$BRANCH_NAME"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if ! ./scripts/add-version.sh "$PACKAGE" "$UPSTREAM_VERSION"; then
echo " WARNING: Failed to update $COMPOSE_FILE for $PACKAGE, skipping"
git checkout -- "$COMPOSE_FILE" || true
continue
fi
git checkout -b "$BRANCH_NAME"
git add "$COMPOSE_FILE"
git commit -m "chore: ${PACKAGE} ${UPSTREAM_VERSION}"
echo " Pushing branch..."
git push -u origin "$BRANCH_NAME"
if ! ./scripts/add-version.sh "$PACKAGE" "$UPSTREAM_VERSION"; then
echo " WARNING: Failed to update $COMPOSE_FILE for $PACKAGE, skipping"
git checkout -- "$COMPOSE_FILE" || true
continue
fi
git checkout main
git pull origin main
git checkout -b "$BRANCH_NAME"
git add "$COMPOSE_FILE"
git commit -m "chore: ${PACKAGE} ${UPSTREAM_VERSION}"
echo " Pushing branch..."
git push -u origin "$BRANCH_NAME"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/check-versions.yml around lines 195 - 206, The script is
creating feature branches from the current (possibly dirty) HEAD via git
checkout -b "$BRANCH_NAME" after running ./scripts/add-version.sh, which can
cause branches to stack unintended changes; before creating each branch (before
git checkout -b "$BRANCH_NAME") ensure you explicitly return to a clean main by
running git checkout main and then git fetch origin && git reset --hard
origin/main (or equivalent) so the new branch starts from the remote/main tip;
apply this change around the code that calls ./scripts/add-version.sh, git add
"$COMPOSE_FILE", git commit -m "chore: ${PACKAGE} ${UPSTREAM_VERSION}", and git
push -u origin "$BRANCH_NAME" to guarantee BRANCH_NAME is created cleanly and
unrelated COMPOSE_FILE changes are not carried forward.

Comment on lines +35 to +52
OLD_VERSION=$(python3 - <<'PY'
import re
import sys

compose_file = sys.argv[1]
compose_var = sys.argv[2]

pattern = re.compile(r"\$\{" + re.escape(compose_var) + r":-([^}]+)\}")
with open(compose_file, "r") as fh:
data = fh.read()

match = pattern.search(data)
if not match:
sys.exit(1)

print(match.group(1))
PY
"$COMPOSE_FILE" "$COMPOSE_VAR" || echo "")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Same heredoc argument bug — sys.argv will be empty.

Same issue as in validate-versions.sh: the "$COMPOSE_FILE" "$COMPOSE_VAR" on Line 52 are parsed as a separate command after the heredoc terminates, not as arguments to python3.

Proposed fix
-OLD_VERSION=$(python3 - <<'PY'
+OLD_VERSION=$(python3 - "$COMPOSE_FILE" "$COMPOSE_VAR" <<'PY'
 import re
 import sys
 
 compose_file = sys.argv[1]
 compose_var = sys.argv[2]
 
 pattern = re.compile(r"\$\{" + re.escape(compose_var) + r":-([^}]+)\}")
 with open(compose_file, "r") as fh:
     data = fh.read()
 
 match = pattern.search(data)
 if not match:
     sys.exit(1)
 
 print(match.group(1))
 PY
-"$COMPOSE_FILE" "$COMPOSE_VAR" || echo "")
+|| echo "")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
OLD_VERSION=$(python3 - <<'PY'
import re
import sys
compose_file = sys.argv[1]
compose_var = sys.argv[2]
pattern = re.compile(r"\$\{" + re.escape(compose_var) + r":-([^}]+)\}")
with open(compose_file, "r") as fh:
data = fh.read()
match = pattern.search(data)
if not match:
sys.exit(1)
print(match.group(1))
PY
"$COMPOSE_FILE" "$COMPOSE_VAR" || echo "")
OLD_VERSION=$(python3 - "$COMPOSE_FILE" "$COMPOSE_VAR" <<'PY'
import re
import sys
compose_file = sys.argv[1]
compose_var = sys.argv[2]
pattern = re.compile(r"\$\{" + re.escape(compose_var) + r":-([^}]+)\}")
with open(compose_file, "r") as fh:
data = fh.read()
match = pattern.search(data)
if not match:
sys.exit(1)
print(match.group(1))
PY
|| echo "")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/add-version.sh` around lines 35 - 52, The heredoc currently runs
python3 without arguments so sys.argv is empty; change the invocation to pass
the two args to python3 before the here-doc (e.g. OLD_VERSION=$(python3 -
"$COMPOSE_FILE" "$COMPOSE_VAR" <<'PY' ... PY "$COMPOSE_FILE" "$COMPOSE_VAR" ||
echo "") ), or equivalently place the "$COMPOSE_FILE" "$COMPOSE_VAR" immediately
after python3 and before the <<'PY' so that the python snippet (which uses
sys.argv[1] and sys.argv[2]) receives the expected arguments; keep the internal
code using sys.argv[1]/sys.argv[2] unchanged.

Comment on lines +64 to +85
if ! python3 - <<'PY'
import re
import sys

compose_file = sys.argv[1]
compose_var = sys.argv[2]
new_version = sys.argv[3]

pattern = re.compile(r"(\$\{" + re.escape(compose_var) + r":-)([^}]+)(\})")
with open(compose_file, "r") as fh:
data = fh.read()

if not pattern.search(data):
print("compose var not found", file=sys.stderr)
sys.exit(1)

updated = pattern.sub(r"\1" + new_version + r"\3", data, count=1)

with open(compose_file, "w") as fh:
fh.write(updated)
PY
"$COMPOSE_FILE" "$COMPOSE_VAR" "$NEW_VERSION"; then
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Same heredoc argument bug in the update block.

Line 85 places "$COMPOSE_FILE" "$COMPOSE_VAR" "$NEW_VERSION" after the heredoc terminator. They need to be on the python3 command line.

Proposed fix
-if ! python3 - <<'PY'
+if ! python3 - "$COMPOSE_FILE" "$COMPOSE_VAR" "$NEW_VERSION" <<'PY'
 import re
 import sys
 
 compose_file = sys.argv[1]
 compose_var = sys.argv[2]
 new_version = sys.argv[3]
 
 pattern = re.compile(r"(\$\{" + re.escape(compose_var) + r":-)([^}]+)(\})")
 with open(compose_file, "r") as fh:
     data = fh.read()
 
 if not pattern.search(data):
     print("compose var not found", file=sys.stderr)
     sys.exit(1)
 
 updated = pattern.sub(r"\1" + new_version + r"\3", data, count=1)
 
 with open(compose_file, "w") as fh:
     fh.write(updated)
 PY
-"$COMPOSE_FILE" "$COMPOSE_VAR" "$NEW_VERSION"; then
+; then
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if ! python3 - <<'PY'
import re
import sys
compose_file = sys.argv[1]
compose_var = sys.argv[2]
new_version = sys.argv[3]
pattern = re.compile(r"(\$\{" + re.escape(compose_var) + r":-)([^}]+)(\})")
with open(compose_file, "r") as fh:
data = fh.read()
if not pattern.search(data):
print("compose var not found", file=sys.stderr)
sys.exit(1)
updated = pattern.sub(r"\1" + new_version + r"\3", data, count=1)
with open(compose_file, "w") as fh:
fh.write(updated)
PY
"$COMPOSE_FILE" "$COMPOSE_VAR" "$NEW_VERSION"; then
if ! python3 - "$COMPOSE_FILE" "$COMPOSE_VAR" "$NEW_VERSION" <<'PY'
import re
import sys
compose_file = sys.argv[1]
compose_var = sys.argv[2]
new_version = sys.argv[3]
pattern = re.compile(r"(\$\{" + re.escape(compose_var) + r":-)([^}]+)(\})")
with open(compose_file, "r") as fh:
data = fh.read()
if not pattern.search(data):
print("compose var not found", file=sys.stderr)
sys.exit(1)
updated = pattern.sub(r"\1" + new_version + r"\3", data, count=1)
with open(compose_file, "w") as fh:
fh.write(updated)
PY
; then
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/add-version.sh` around lines 64 - 85, The heredoc invocation for the
Python block is incorrect: the arguments "$COMPOSE_FILE" "$COMPOSE_VAR"
"$NEW_VERSION" are placed after the PY terminator instead of on the python3
command line; move those three arguments to the python3 invocation so the inline
script receives them (i.e., call python3 - <<'PY' with the arguments immediately
following python3, ensuring the script uses sys.argv[1..3] for compose_file,
compose_var, new_version), leaving the rest of the block (pattern, updated, file
writes) unchanged.

Comment on lines +66 to +111
UPSTREAM_TAG=$(python3 - <<'PY'
import json
import re
import sys
import urllib.request

image = sys.argv[1]
regex = re.compile(sys.argv[2])

page = 1
page_size = 100
found = []

while True:
url = f"https://hub.docker.com/v2/repositories/{image}/tags?page={page}&page_size={page_size}"
with urllib.request.urlopen(url) as resp:
data = json.loads(resp.read().decode("utf-8"))

for item in data.get("results", []):
tag = item.get("name", "")
if regex.match(tag):
found.append(tag)

if not data.get("next"):
break
page += 1

if not found:
sys.exit(1)

def normalize(tag):
if tag.startswith("v"):
tag = tag[1:]
parts = re.split(r"[.-]", tag)
key = []
for part in parts:
if part.isdigit():
key.append(int(part))
else:
key.append(part)
return key

found.sort(key=normalize)
print(found[-1])
PY
"$DOCKER_IMAGE" "$TAG_REGEX" || echo "")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Same heredoc argument bug — Docker Hub Python snippet won't receive arguments.

Line 111: "$DOCKER_IMAGE" "$TAG_REGEX" appears after the heredoc terminator and won't be passed to python3.

Proposed fix
-    UPSTREAM_TAG=$(python3 - <<'PY'
+    UPSTREAM_TAG=$(python3 - "$DOCKER_IMAGE" "$TAG_REGEX" <<'PY'
 ...
 PY
-"$DOCKER_IMAGE" "$TAG_REGEX" || echo "")
+|| echo "")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
UPSTREAM_TAG=$(python3 - <<'PY'
import json
import re
import sys
import urllib.request
image = sys.argv[1]
regex = re.compile(sys.argv[2])
page = 1
page_size = 100
found = []
while True:
url = f"https://hub.docker.com/v2/repositories/{image}/tags?page={page}&page_size={page_size}"
with urllib.request.urlopen(url) as resp:
data = json.loads(resp.read().decode("utf-8"))
for item in data.get("results", []):
tag = item.get("name", "")
if regex.match(tag):
found.append(tag)
if not data.get("next"):
break
page += 1
if not found:
sys.exit(1)
def normalize(tag):
if tag.startswith("v"):
tag = tag[1:]
parts = re.split(r"[.-]", tag)
key = []
for part in parts:
if part.isdigit():
key.append(int(part))
else:
key.append(part)
return key
found.sort(key=normalize)
print(found[-1])
PY
"$DOCKER_IMAGE" "$TAG_REGEX" || echo "")
UPSTREAM_TAG=$(python3 - "$DOCKER_IMAGE" "$TAG_REGEX" <<'PY'
import json
import re
import sys
import urllib.request
image = sys.argv[1]
regex = re.compile(sys.argv[2])
page = 1
page_size = 100
found = []
while True:
url = f"https://hub.docker.com/v2/repositories/{image}/tags?page={page}&page_size={page_size}"
with urllib.request.urlopen(url) as resp:
data = json.loads(resp.read().decode("utf-8"))
for item in data.get("results", []):
tag = item.get("name", "")
if regex.match(tag):
found.append(tag)
if not data.get("next"):
break
page += 1
if not found:
sys.exit(1)
def normalize(tag):
if tag.startswith("v"):
tag = tag[1:]
parts = re.split(r"[.-]", tag)
key = []
for part in parts:
if part.isdigit():
key.append(int(part))
else:
key.append(part)
return key
found.sort(key=normalize)
print(found[-1])
PY
|| echo "")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/check-versions.sh` around lines 66 - 111, The python heredoc in the
UPSTREAM_TAG assignment isn't receiving the shell arguments because
"$DOCKER_IMAGE" "$TAG_REGEX" are placed after the heredoc terminator; move the
arguments into the python3 command invocation so they are passed as sys.argv
(e.g., call python3 - "$DOCKER_IMAGE" "$TAG_REGEX" <<'PY' ... PY), leaving the
script body (including the normalize function) unchanged and ensuring
sys.argv[1] and sys.argv[2] map to DOCKER_IMAGE and TAG_REGEX.

Comment on lines +128 to +145
LOCAL_VERSION=$(python3 - <<'PY'
import re
import sys

compose_file = sys.argv[1]
compose_var = sys.argv[2]

pattern = re.compile(r"\$\{" + re.escape(compose_var) + r":-([^}]+)\}")
with open(compose_file, "r") as fh:
data = fh.read()

match = pattern.search(data)
if not match:
sys.exit(1)

print(match.group(1))
PY
"$COMPOSE_FILE" "$COMPOSE_VAR" || echo "")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Same heredoc argument bug — local version extraction will fail.

Line 145: "$COMPOSE_FILE" "$COMPOSE_VAR" appears after the heredoc terminator.

Proposed fix
-  LOCAL_VERSION=$(python3 - <<'PY'
+  LOCAL_VERSION=$(python3 - "$COMPOSE_FILE" "$COMPOSE_VAR" <<'PY'
 ...
 PY
-"$COMPOSE_FILE" "$COMPOSE_VAR" || echo "")
+|| echo "")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/check-versions.sh` around lines 128 - 145, The heredoc feeding the
inline Python to python3 is terminated before the "$COMPOSE_FILE" "$COMPOSE_VAR"
arguments are passed, so LOCAL_VERSION is always empty; move the arguments into
the heredoc invocation so python3 receives sys.argv (i.e., ensure the python3
<<'PY' block is followed immediately by the PY terminator and then call python3
with "$COMPOSE_FILE" "$COMPOSE_VAR" inside the same command), or alternatively
change to a single-line python3 -c invocation that accepts "$COMPOSE_FILE"
"$COMPOSE_VAR"; update the LOCAL_VERSION assignment to run python3 with the
inline script (reading compose_file and compose_var from sys.argv) so
pattern.search and match.group(1) execute correctly and the command returns the
version.

Comment on lines +34 to +38
PATTERN="\\${${COMPOSE_VAR}:-"
if ! grep -q "$PATTERN" "$COMPOSE_FILE"; then
echo "ERROR: ${COMPOSE_VAR} not found in $COMPOSE_FILE"
ERRORS=$((ERRORS + 1))
continue
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Broken shell quoting in grep pattern — script will error or misbehave.

PATTERN="\\${${COMPOSE_VAR}:-" attempts to construct a literal string like \${CARDANO_NODE_VERSION:-, but inside double quotes, ${…} triggers parameter expansion. Bash will try to interpret ${${COMPOSE_VAR}:-" as a (malformed) nested expansion, leading to a parse error or unexpected behavior. This matches the ShellCheck SC1078/SC1073 warnings.

Use single quotes to avoid expansion for the literal parts:

Proposed fix
-  PATTERN="\\${${COMPOSE_VAR}:-"
-  if ! grep -q "$PATTERN" "$COMPOSE_FILE"; then
+  PATTERN='\$\{'"${COMPOSE_VAR}"':-'
+  if ! grep -q "$PATTERN" "$COMPOSE_FILE"; then

Alternatively, use grep -F with a literal (non-regex) match:

-  PATTERN="\\${${COMPOSE_VAR}:-"
-  if ! grep -q "$PATTERN" "$COMPOSE_FILE"; then
+  PATTERN='${'"${COMPOSE_VAR}"':-'
+  if ! grep -Fq "$PATTERN" "$COMPOSE_FILE"; then
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
PATTERN="\\${${COMPOSE_VAR}:-"
if ! grep -q "$PATTERN" "$COMPOSE_FILE"; then
echo "ERROR: ${COMPOSE_VAR} not found in $COMPOSE_FILE"
ERRORS=$((ERRORS + 1))
continue
PATTERN='\$\{'"${COMPOSE_VAR}"':-'
if ! grep -q "$PATTERN" "$COMPOSE_FILE"; then
echo "ERROR: ${COMPOSE_VAR} not found in $COMPOSE_FILE"
ERRORS=$((ERRORS + 1))
continue
🧰 Tools
🪛 Shellcheck (0.11.0)

[warning] 35-35: Did you forget to close this double quoted string?

(SC1078)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/validate-versions.sh` around lines 34 - 38, The PATTERN assignment
uses double quotes which cause unintended parameter expansion; update the
PATTERN construction so the literal characters "\${" and ":-" are not expanded
(e.g., build PATTERN using single quotes for the fixed pieces or concatenate
quoted literals and the variable: use PATTERN='\\${'"${COMPOSE_VAR}"':-' or
switch to a literal search like grep -F -- "$PATTERN" "$COMPOSE_FILE") and
ensure the subsequent grep uses the appropriate option (or proper escaping) so
grep searches for the literal string; adjust references to PATTERN, COMPOSE_VAR,
COMPOSE_FILE, and the grep invocation accordingly.

Comment on lines +41 to +58
DEFAULT_VERSION=$(python3 - <<'PY'
import re
import sys

compose_file = sys.argv[1]
compose_var = sys.argv[2]

pattern = re.compile(r"\$\{" + re.escape(compose_var) + r":-([^}]+)\}")
with open(compose_file, "r") as fh:
data = fh.read()

match = pattern.search(data)
if not match:
sys.exit(1)

print(match.group(1))
PY
"$COMPOSE_FILE" "$COMPOSE_VAR" || echo "")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Heredoc arguments not passed to python3sys.argv[1] will raise IndexError.

The positional arguments "$COMPOSE_FILE" "$COMPOSE_VAR" appear on Line 58, after the heredoc terminator PY on Line 57. Bash treats them as a separate command, not as arguments to python3. The Python script accesses sys.argv[1] and sys.argv[2], which won't exist, causing an IndexError.

Place the arguments on the initial command line, before the heredoc redirect:

Proposed fix
-  DEFAULT_VERSION=$(python3 - <<'PY'
+  DEFAULT_VERSION=$(python3 - "$COMPOSE_FILE" "$COMPOSE_VAR" <<'PY'
 import re
 import sys
 
 compose_file = sys.argv[1]
 compose_var = sys.argv[2]
 
 pattern = re.compile(r"\$\{" + re.escape(compose_var) + r":-([^}]+)\}")
 with open(compose_file, "r") as fh:
     data = fh.read()
 
 match = pattern.search(data)
 if not match:
     sys.exit(1)
 
 print(match.group(1))
 PY
-"$COMPOSE_FILE" "$COMPOSE_VAR" || echo "")
+|| echo "")

This same bug exists in scripts/add-version.sh (Lines 35–52 and 64–85) and scripts/check-versions.sh (Lines 128–145) — all heredoc invocations need the same fix.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/validate-versions.sh` around lines 41 - 58, The heredoc Python
snippet that computes DEFAULT_VERSION is currently invoked without passing
"$COMPOSE_FILE" and "$COMPOSE_VAR" to python3 (so sys.argv[1]/[2] will be
missing); fix by supplying those positional args on the python3 command line
(i.e., pass "$COMPOSE_FILE" "$COMPOSE_VAR" immediately after the python3
invocation and before the <<'PY' heredoc token) for DEFAULT_VERSION and apply
the same change to the equivalent heredoc calls in scripts/add-version.sh and
scripts/check-versions.sh so the Python code can safely access sys.argv[1] and
sys.argv[2].

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

3 issues found across 7 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name=".github/workflows/check-versions.yml">

<violation number="1" location=".github/workflows/check-versions.yml:68">
P2: The default TAG_REGEX incorrectly escapes the dot (`\\.`), so it won’t match normal semver tags like `1.2.3`. This causes `UPSTREAM_TAG` to stay empty and the workflow to skip updates. Use a single escaped dot in the regex.</violation>
</file>

<file name="scripts/check-versions.sh">

<violation number="1" location="scripts/check-versions.sh:111">
P2: The Docker Hub Python helper never receives its CLI arguments because they’re placed after the heredoc terminator. This causes the script to fall back to an empty UPSTREAM_TAG and skip updates for Docker Hub images.</violation>

<violation number="2" location="scripts/check-versions.sh:145">
P2: The compose version lookup never receives its CLI arguments because they’re placed after the heredoc terminator, so local versions are always treated as missing.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

fi

if [[ -z "$TAG_REGEX" ]]; then
TAG_REGEX='^[0-9]+(\\.[0-9]+){2,3}$'
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 18, 2026

Choose a reason for hiding this comment

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

P2: The default TAG_REGEX incorrectly escapes the dot (\\.), so it won’t match normal semver tags like 1.2.3. This causes UPSTREAM_TAG to stay empty and the workflow to skip updates. Use a single escaped dot in the regex.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/check-versions.yml, line 68:

<comment>The default TAG_REGEX incorrectly escapes the dot (`\\.`), so it won’t match normal semver tags like `1.2.3`. This causes `UPSTREAM_TAG` to stay empty and the workflow to skip updates. Use a single escaped dot in the regex.</comment>

<file context>
@@ -0,0 +1,233 @@
+            fi
+
+            if [[ -z "$TAG_REGEX" ]]; then
+              TAG_REGEX='^[0-9]+(\\.[0-9]+){2,3}$'
+            fi
+
</file context>
Fix with Cubic


print(match.group(1))
PY
"$COMPOSE_FILE" "$COMPOSE_VAR" || echo "")
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 18, 2026

Choose a reason for hiding this comment

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

P2: The compose version lookup never receives its CLI arguments because they’re placed after the heredoc terminator, so local versions are always treated as missing.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/check-versions.sh, line 145:

<comment>The compose version lookup never receives its CLI arguments because they’re placed after the heredoc terminator, so local versions are always treated as missing.</comment>

<file context>
@@ -0,0 +1,183 @@
+
+print(match.group(1))
+PY
+"$COMPOSE_FILE" "$COMPOSE_VAR" || echo "")
+
+  if [[ -z "$LOCAL_VERSION" ]]; then
</file context>
Fix with Cubic

found.sort(key=normalize)
print(found[-1])
PY
"$DOCKER_IMAGE" "$TAG_REGEX" || echo "")
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 18, 2026

Choose a reason for hiding this comment

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

P2: The Docker Hub Python helper never receives its CLI arguments because they’re placed after the heredoc terminator. This causes the script to fall back to an empty UPSTREAM_TAG and skip updates for Docker Hub images.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/check-versions.sh, line 111:

<comment>The Docker Hub Python helper never receives its CLI arguments because they’re placed after the heredoc terminator. This causes the script to fall back to an empty UPSTREAM_TAG and skip updates for Docker Hub images.</comment>

<file context>
@@ -0,0 +1,183 @@
+found.sort(key=normalize)
+print(found[-1])
+PY
+"$DOCKER_IMAGE" "$TAG_REGEX" || echo "")
+
+    if [[ -z "$UPSTREAM_TAG" ]]; then
</file context>
Fix with Cubic

Signed-off-by: Chris Gianelloni <wolf31o2@blinklabs.io>
@wolf31o2 wolf31o2 force-pushed the ci/automated-updates branch from dc40451 to 37e562e Compare February 24, 2026 14:57
@wolf31o2
Copy link
Member Author

@cubic-dev-ai review

@cubic-dev-ai
Copy link

cubic-dev-ai bot commented Feb 28, 2026

@cubic-dev-ai review

@wolf31o2 I have started the AI code review. It will take a few minutes to complete.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 7 files

@wolf31o2 wolf31o2 merged commit af61f6e into main Feb 28, 2026
2 checks passed
@wolf31o2 wolf31o2 deleted the ci/automated-updates branch February 28, 2026 22:02
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