Skip to content

Atomic zip writes + restore composer tooling on packaging failure (follow-up to #31) - #32

Open
pattonwebz wants to merge 5 commits into
mainfrom
william/pro-1196-followup-atomic-zip-and-restore
Open

Atomic zip writes + restore composer tooling on packaging failure (follow-up to #31)#32
pattonwebz wants to merge 5 commits into
mainfrom
william/pro-1196-followup-atomic-zip-and-restore

Conversation

@pattonwebz

@pattonwebz pattonwebz commented Jul 17, 2026

Copy link
Copy Markdown
Member

What & why

Follow-up to CodeRabbit's second review pass on #31, which landed after that PR was already merged (per instruction, ahead of the slow-that-day review). Two real findings on the set -e hardening from #31 itself:

  1. Zip written directly to dist/zip -r wrote straight to the final release path. A mid-write failure (disk full, permissions) could leave a truncated file sitting at dist/*.zip, indistinguishable from a good one without re-checking.
  2. My own set -e fix regressed the composer-restore guarantee — the trailing composer install (restoring dev tooling) only ran on the happy path. With set -e active, a cp/zip failure now aborts before reaching it, silently leaving vendor/ in --no-dev mode (no phpcs/phpunit) for the rest of the session — exactly the failure mode rule 2 already warned about, just newly reachable because of the hardening itself.

Fix

  • Zip is built inside $TMP_DIR, mv'd into dist/ only after zip succeeds.
  • Folded the composer restore into the existing cleanup trap so it runs on every exit path, not just success — guarded with || true so a restore hiccup can't mask the script's real exit code (captured via local exit_code=$? at the top of the trap, re-raised at the end via exit "$exit_code").

Verification

Ran three real script executions, capturing actual exit codes (not just reading the diff):

  • Happy path: correct 54-file zip, dev tooling restored.
  • Forced zip failure (pointed at a nonexistent staged directory): script now propagates a real nonzero exit code (12), no partial zip lands in dist/, and phpcs/phpunit are still restored despite the failure.
  • Rebuilt the actual release dist/boardscribe.zip with the corrected recipe, confirmed manifest (0 composer.json — still excluded per the earlier PRO-1196 decision, 54 files).
  • composer check-cs, PHPUnit 90/90 clean.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation
    • Updated plugin packaging instructions for free and Pro editions.
    • Added safer packaging requirements, including consistent cleanup and restoration of development tooling.
    • Improved archive creation guidance so packages are moved to the distribution folder only after successful creation.

pattonwebz and others added 5 commits July 16, 2026 23:02
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Addresses two findings from CodeRabbit's second review pass on #31
(landed after that PR was already merged, so following up here):

1. zip -r wrote directly to dist/*.zip. A mid-write failure (disk
   full, permissions) could leave a truncated file sitting at the
   release path, indistinguishable from a good one without
   re-checking. Now zips into $TMP_DIR and mv's into dist/ only after
   zip succeeds.

2. My own set -e fix in the prior commit introduced a regression: the
   trailing 'composer install' (restoring dev tooling) only ran on the
   happy path, since set -e now aborts the script before reaching it
   if an earlier cp/zip step fails - silently leaving vendor/ in
   --no-dev mode (no phpcs/phpunit) for the rest of the session, which
   is exactly the failure mode rule 2 already warned about. Folded the
   restore into the same cleanup trap (guarded with '|| true' so a
   restore hiccup can't mask the script's real exit code, which the
   trap captures via 'local exit_code=$?' before doing anything else
   and re-raises at the end).

Verified with three real runs, capturing the actual exit code (not
just glancing at the diff): happy path still produces a correct
54-file zip with dev tooling restored; a forced zip failure now
propagates a real nonzero exit code (12), leaves no partial zip in
dist/, and still restores phpcs/phpunit despite the failure.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@linear-code

linear-code Bot commented Jul 17, 2026

Copy link
Copy Markdown
PRO-1196 Run WordPress Plugin Check against the packaged release

Problem

The official Plugin Check has not yet been run against the exact release ZIP. Local PHPCS and CI do not cover every WordPress.org repository-specific check.

Acceptance criteria

  • Run the current official Plugin Check against dist/meeting-minutes.zip.
  • All checks in the “Plugin repo” category pass.
  • Review warnings from security, performance, accessibility, and best-practice categories.
  • Fix actionable findings or document justified false positives.
  • Rebuild the ZIP after any changes and rerun Plugin Check.
  • Attach or link the final Plugin Check output to this issue.

Review in Linear

@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Packaging documentation now standardizes cleanup for Free and Pro plugin archives, restores Composer tooling on all exits, removes temporary files, and moves completed ZIPs into dist/ only after successful creation.

Changes

Plugin packaging instructions

Layer / File(s) Summary
Packaging script requirements
.claude/skills/package-plugins/SKILL.md
Critical rules require strict bash mode, a cleanup trap, Composer restoration, temporary-directory removal, and safe ZIP publication.
Free and Pro packaging recipes
.claude/skills/package-plugins/SKILL.md
Both recipes create ZIPs in temporary storage, then move successful archives into dist/; cleanup restores Composer tooling with composer install || true.

Estimated code review effort: 1 (Trivial) | ~5 minutes

Possibly related PRs

Poem

A rabbit zipped through temp space bright,
Then moved the archive when it was right.
Composer returned, the scraps blew away,
Free and Pro packed safely today.
“Hop!” said the bunny, “clean builds all the way!”

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR updates packaging scripts, but it does not show the required official Plugin Check run against the release ZIP or its results. Run the official Plugin Check on the exact packaged ZIP, fix or document findings, rebuild if needed, and attach the final output.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main packaging change: atomic ZIP writes and cleanup-based Composer restore handling.
Out of Scope Changes check ✅ Passed The changes stay focused on packaging safety and cleanup behavior, with no obvious unrelated additions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with 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.

Inline comments:
In @.claude/skills/package-plugins/SKILL.md:
- Around line 31-37: Move the cleanup trap setup to immediately after strict
mode, before composer install --no-dev and any filesystem operations. Initialize
TMP_DIR to an empty value first, and update cleanup() to remove the temporary
directory only when TMP_DIR is non-empty while preserving the exit code and
restoring dev tooling.
- Around line 17-19: Update the packaging workflow documentation after the ZIP
build to require running the official WordPress Plugin Check against the exact
packaged ZIP, reviewing plugin_repo warnings, documenting actionable findings,
and rebuilding and retesting as needed. Require retaining the final Plugin Check
output, while preserving the existing manifest verification step.
- Around line 34-35: Update both cleanup trap blocks in the package plugin
workflow so the rm -rf operation is explicitly non-fatal, ensuring cleanup
failures cannot interrupt execution before the saved exit status is returned.
Preserve each block’s existing exit "$exit_code" behavior and apply the same
guard consistently to both traps.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 63160d8f-310f-4cbd-b86a-76504de64c26

📥 Commits

Reviewing files that changed from the base of the PR and between 1efc2ab and 5802731.

📒 Files selected for processing (1)
  • .claude/skills/package-plugins/SKILL.md

Comment on lines +17 to +19
6. **Run each plugin's whole bash block as a single script**, not line-by-line — `$STAGE`/`$TMP_DIR` are shell variables/state that don't persist across separate command invocations, and the `mv` into `dist/` after the zip subshell relies on still being in the repo root.
7. **Both blocks open with `set -euo pipefail` and a `cleanup` trap — don't strip these when copy-pasting.** Without them, a failed `composer install`/`cp`/`zip` can be silently ignored and the script continues packaging a broken zip; worse, if `mktemp -d` itself fails, `STAGE=$(mktemp -d)/boardscribe` degrades to the literal string `/boardscribe` with **no error surfaced** (bash doesn't propagate the failure through the concatenation), making a naive cleanup line `rm -rf "$(dirname "$STAGE")"` resolve to `rm -rf /`. The current form avoids this entirely: `TMP_DIR=$(mktemp -d)` is a bare assignment (so `set -e` reliably aborts if it fails, unlike the concatenated form), `STAGE` is built from `$TMP_DIR` afterward, and the `cleanup` function only ever removes that one known path — it can't be redirected by a failure elsewhere. (Found via CodeRabbit review on boardscribe#31.)
8. **The `cleanup` trap does two things, in order, and both matter under `set -e`:** (1) restores dev tooling with a plain `composer install` — this must happen on **every** exit path, not just the happy one, because with `set -e` active a `cp`/`zip` failure now aborts the script *before* reaching a trailing `composer install` line, which would silently leave `vendor/` in `--no-dev` mode (no phpcs/phpunit) for the rest of the session; (2) removes `$TMP_DIR`. The restore step is guarded with `|| true` so a Composer hiccup during cleanup can't itself mask the script's real exit code, which the trap captures up front (`local exit_code=$?`) and re-raises at the end via `exit "$exit_code"`. **The zip is built inside `$TMP_DIR` and `mv`'d into `dist/` only after `zip` succeeds** — writing `zip -r` directly to the final `dist/*.zip` path means a mid-write failure (disk full, permissions) can leave a truncated file sitting at the release path, indistinguishable from a good one without re-checking. (Also found via CodeRabbit review on boardscribe#31.)

Copy link
Copy Markdown

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

Document the required Plugin Check release gate.

PRO-1196 requires running the official WordPress Plugin Check against the exact packaged ZIP, reviewing plugin_repo warnings, documenting actionable findings, rebuilding/retesting, and retaining the final output. The current workflow only verifies the manifest with unzip -l, so add this explicit post-build step.

🧰 Tools
🪛 SkillSpector (2.3.11)

[warning] 15: [EA2] Autonomous Decision Making: Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Remediation: Add human-in-the-loop confirmation for destructive, irreversible, or high-impact operations. Never auto-execute commands that modify files, send data, or alter system state.

(Excessive Agency (EA2))


[error] 18: [TM1] Tool Parameter Abuse: Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Remediation: Validate all tool parameters against an allowlist. Reject dangerous parameter values (shell=True, --force, -rf /) and use safe defaults.

(Tool Misuse (TM1))


[error] 18: [TM1] Tool Parameter Abuse: Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Remediation: Validate all tool parameters against an allowlist. Reject dangerous parameter values (shell=True, --force, -rf /) and use safe defaults.

(Tool Misuse (TM1))


[error] 28: [TM1] Tool Parameter Abuse: Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Remediation: Validate all tool parameters against an allowlist. Reject dangerous parameter values (shell=True, --force, -rf /) and use safe defaults.

(Tool Misuse (TM1))


[error] 60: [TM1] Tool Parameter Abuse: Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Remediation: Validate all tool parameters against an allowlist. Reject dangerous parameter values (shell=True, --force, -rf /) and use safe defaults.

(Tool Misuse (TM1))


[error] 28: [TM2] Chaining Abuse: Tool calls are chained to bypass individual safety checks or escalate capabilities beyond what any single tool call would allow.

Remediation: Limit tool chaining depth and validate the output of each tool before passing it to the next. Require explicit user approval for multi-step chains.

(Tool Misuse (TM2))


[error] 60: [TM2] Chaining Abuse: Tool calls are chained to bypass individual safety checks or escalate capabilities beyond what any single tool call would allow.

Remediation: Limit tool chaining depth and validate the output of each tool before passing it to the next. Require explicit user approval for multi-step chains.

(Tool Misuse (TM2))

🤖 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 @.claude/skills/package-plugins/SKILL.md around lines 17 - 19, Update the
packaging workflow documentation after the ZIP build to require running the
official WordPress Plugin Check against the exact packaged ZIP, reviewing
plugin_repo warnings, documenting actionable findings, and rebuilding and
retesting as needed. Require retaining the final Plugin Check output, while
preserving the existing manifest verification step.

Comment on lines +31 to +37
cleanup() {
local exit_code=$?
composer install || true # restore dev tooling (phpcs/phpunit/etc.) - even on failure
rm -rf -- "$TMP_DIR"
exit "$exit_code"
}
trap cleanup EXIT

Copy link
Copy Markdown

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

Install the EXIT trap before composer install --no-dev.

The trap is registered only after Line 27/Line 59 and the dist cleanup. If either composer install --no-dev or the preceding filesystem operation fails under set -e, the script exits without restoring Composer development tooling. Initialize TMP_DIR to an empty value and register the trap immediately after strict mode, guarding the empty path.

Proposed ordering
 set -euo pipefail
+TMP_DIR=""
+cleanup() {
+  local exit_code=$?
+  composer install || true
+  if [[ -n "$TMP_DIR" ]]; then
+    rm -rf -- "$TMP_DIR"
+  fi
+  exit "$exit_code"
+}
+trap cleanup EXIT
+
 npm run build
 composer install --no-dev --optimize-autoloader
 ...
-TMP_DIR=$(mktemp -d)
+TMP_DIR=$(mktemp -d)
 STAGE="$TMP_DIR/boardscribe"
-cleanup() {
-...
-}
-trap cleanup EXIT

Also applies to: 63-69

🧰 Tools
🪛 SkillSpector (2.3.11)

[warning] 15: [EA2] Autonomous Decision Making: Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Remediation: Add human-in-the-loop confirmation for destructive, irreversible, or high-impact operations. Never auto-execute commands that modify files, send data, or alter system state.

(Excessive Agency (EA2))


[error] 18: [TM1] Tool Parameter Abuse: Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Remediation: Validate all tool parameters against an allowlist. Reject dangerous parameter values (shell=True, --force, -rf /) and use safe defaults.

(Tool Misuse (TM1))


[error] 18: [TM1] Tool Parameter Abuse: Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Remediation: Validate all tool parameters against an allowlist. Reject dangerous parameter values (shell=True, --force, -rf /) and use safe defaults.

(Tool Misuse (TM1))


[error] 28: [TM1] Tool Parameter Abuse: Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Remediation: Validate all tool parameters against an allowlist. Reject dangerous parameter values (shell=True, --force, -rf /) and use safe defaults.

(Tool Misuse (TM1))


[error] 60: [TM1] Tool Parameter Abuse: Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Remediation: Validate all tool parameters against an allowlist. Reject dangerous parameter values (shell=True, --force, -rf /) and use safe defaults.

(Tool Misuse (TM1))


[error] 28: [TM2] Chaining Abuse: Tool calls are chained to bypass individual safety checks or escalate capabilities beyond what any single tool call would allow.

Remediation: Limit tool chaining depth and validate the output of each tool before passing it to the next. Require explicit user approval for multi-step chains.

(Tool Misuse (TM2))


[error] 60: [TM2] Chaining Abuse: Tool calls are chained to bypass individual safety checks or escalate capabilities beyond what any single tool call would allow.

Remediation: Limit tool chaining depth and validate the output of each tool before passing it to the next. Require explicit user approval for multi-step chains.

(Tool Misuse (TM2))

🤖 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 @.claude/skills/package-plugins/SKILL.md around lines 31 - 37, Move the
cleanup trap setup to immediately after strict mode, before composer install
--no-dev and any filesystem operations. Initialize TMP_DIR to an empty value
first, and update cleanup() to remove the temporary directory only when TMP_DIR
is non-empty while preserving the exit code and restoring dev tooling.

Comment on lines +34 to +35
rm -rf -- "$TMP_DIR"
exit "$exit_code"

Copy link
Copy Markdown

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

🧩 Analysis chain

🏁 Script executed:

sed -n '1,120p' .claude/skills/package-plugins/SKILL.md | cat -n

Repository: equalizedigital/boardscribe

Length of output: 10366


🏁 Script executed:

python3 - <<'PY'
import os, tempfile, subprocess, textwrap, pathlib, json, sys
script = r'''#!/usr/bin/env bash
set -euo pipefail
tmp=$(mktemp -d)
mkdir "$tmp/dir"
chmod 500 "$tmp"
exit_code=42
cleanup() {
  local exit_code=$?
  echo "trap-start:$exit_code"
  rm -rf -- "$tmp/dir"
  echo "trap-after-rm"
  exit "$exit_code"
}
trap cleanup EXIT
false
'''
p = subprocess.run(["bash", "-lc", script], capture_output=True, text=True)
print("rc:", p.returncode)
print("stdout:")
print(p.stdout)
print("stderr:")
print(p.stderr)
PY

Repository: equalizedigital/boardscribe

Length of output: 265


Guard both cleanup traps so rm can’t hide the real exit code.
rm -rf runs under set -e; if it fails, the trap exits before exit "$exit_code", so the original status is lost. Make the removal non-fatal and then re-raise the saved code in both cleanup blocks.

🧰 Tools
🪛 SkillSpector (2.3.11)

[warning] 15: [EA2] Autonomous Decision Making: Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Remediation: Add human-in-the-loop confirmation for destructive, irreversible, or high-impact operations. Never auto-execute commands that modify files, send data, or alter system state.

(Excessive Agency (EA2))


[error] 18: [TM1] Tool Parameter Abuse: Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Remediation: Validate all tool parameters against an allowlist. Reject dangerous parameter values (shell=True, --force, -rf /) and use safe defaults.

(Tool Misuse (TM1))


[error] 18: [TM1] Tool Parameter Abuse: Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Remediation: Validate all tool parameters against an allowlist. Reject dangerous parameter values (shell=True, --force, -rf /) and use safe defaults.

(Tool Misuse (TM1))


[error] 28: [TM1] Tool Parameter Abuse: Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Remediation: Validate all tool parameters against an allowlist. Reject dangerous parameter values (shell=True, --force, -rf /) and use safe defaults.

(Tool Misuse (TM1))


[error] 60: [TM1] Tool Parameter Abuse: Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Remediation: Validate all tool parameters against an allowlist. Reject dangerous parameter values (shell=True, --force, -rf /) and use safe defaults.

(Tool Misuse (TM1))


[error] 28: [TM2] Chaining Abuse: Tool calls are chained to bypass individual safety checks or escalate capabilities beyond what any single tool call would allow.

Remediation: Limit tool chaining depth and validate the output of each tool before passing it to the next. Require explicit user approval for multi-step chains.

(Tool Misuse (TM2))


[error] 60: [TM2] Chaining Abuse: Tool calls are chained to bypass individual safety checks or escalate capabilities beyond what any single tool call would allow.

Remediation: Limit tool chaining depth and validate the output of each tool before passing it to the next. Require explicit user approval for multi-step chains.

(Tool Misuse (TM2))

🤖 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 @.claude/skills/package-plugins/SKILL.md around lines 34 - 35, Update both
cleanup trap blocks in the package plugin workflow so the rm -rf operation is
explicitly non-fatal, ensuring cleanup failures cannot interrupt execution
before the saved exit status is returned. Preserve each block’s existing exit
"$exit_code" behavior and apply the same guard consistently to both traps.

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.

1 participant