diff --git a/CLAUDE.md b/CLAUDE.md index 3e8ebca..0db10eb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -40,6 +40,19 @@ docker compose exec app drush riverside:rebuild docker compose exec app drush rrb ``` +**Note on semaphore/lock errors in logs:** During rebuilds (especially the full non-`DRUPAL_FAST` path) you may see Postgres errors like: + +``` +ERROR: duplicate key value violates unique constraint "semaphore____pkey" +DETAIL: Key (name)=(state:Drupal\Core\Cache\CacheCollector) already exists. +``` + +This is harmless but noisy. The entrypoint and `_riverside_pt_rebuild()` proactively `TRUNCATE TABLE semaphore` at key points to suppress them. If you ever see them after a manual change, run: + +```bash +docker compose exec app drush sql:query "TRUNCATE TABLE semaphore;" +``` + The custom module directory is volume-mounted, so template/CSS/JS edits are live without rebuilding the Docker image. `settings.php` and `development.services.yml` are also volume-mounted. **Known gotcha:** `drush site:install` rewrites `settings.php`. Because `settings.php` is a bind-mounted file, Docker Desktop on macOS may hold a stale inode reference after the rewrite. If Drupal shows "The provided host name is not valid for this server" after a full rebuild, restart with `DRUPAL_FAST=1` to re-establish the mount without re-running site:install. diff --git a/docker/php/entrypoint.sh b/docker/php/entrypoint.sh index bee8f42..67609b4 100644 --- a/docker/php/entrypoint.sh +++ b/docker/php/entrypoint.sh @@ -37,8 +37,17 @@ else --account-pass="$ADMIN_PASS" \ -y || { echo "[entrypoint] FATAL: site:install failed."; exit 1; } echo "[entrypoint] Drupal installed." + + # Clear semaphores immediately after fresh install (prevents early + # duplicate key errors during first module enables + rebuild). + $DRUSH sql:query "TRUNCATE TABLE semaphore;" 2>/dev/null || true fi +# Always clear stale semaphores before module enables + rebuild. +# This is the most reliable way to avoid the duplicate key errors +# on "semaphore" (CacheCollector, cron, state locks, etc.). +$DRUSH sql:query "TRUNCATE TABLE semaphore;" 2>/dev/null || true + echo "[entrypoint] Enabling required modules..." $DRUSH en -y views views_ui field_ui text options link datetime && \ echo "[entrypoint] Core modules enabled." || echo "[entrypoint] WARNING: core modules failed." @@ -49,6 +58,11 @@ $DRUSH en -y symfony_mailer && \ $DRUSH en -y riverside_pt && \ echo "[entrypoint] riverside_pt enabled." || echo "[entrypoint] WARNING: riverside_pt failed." +# Clear semaphores to avoid duplicate key violations on the semaphore +# table (e.g. during CacheCollector, cron, state operations) that can +# occur during rapid config/entity changes in the rebuild. +$DRUSH sql:query "TRUNCATE TABLE semaphore;" 2>/dev/null || true + echo "[entrypoint] Rebuilding site structure from code (riverside:rebuild)..." $DRUSH riverside:rebuild || echo "[entrypoint] WARNING: riverside:rebuild encountered an issue." @@ -63,6 +77,10 @@ $DRUSH config:set system.site page.front /home -y && \ npm run build --prefix /var/www/html >/dev/null 2>&1 && echo "[entrypoint] Tailwind built." || echo "[entrypoint] WARNING: Tailwind build failed." +# One more semaphore clear before the final cache rebuild (common source +# of the duplicate key errors seen in logs). +$DRUSH sql:query "TRUNCATE TABLE semaphore;" 2>/dev/null || true + $DRUSH cache:rebuild >/dev/null 2>&1 && echo "[entrypoint] Cache rebuilt." if [ "${DEBUG:-false}" = "true" ]; then diff --git a/web/modules/custom/riverside_pt/riverside_pt.install b/web/modules/custom/riverside_pt/riverside_pt.install index 46b749d..f6895ca 100644 --- a/web/modules/custom/riverside_pt/riverside_pt.install +++ b/web/modules/custom/riverside_pt/riverside_pt.install @@ -26,6 +26,18 @@ function riverside_pt_install() { * and navigation used by the Docker entrypoint. */ function _riverside_pt_rebuild(): void { + // Clear any stale semaphore locks. This prevents duplicate key violations + // on the "semaphore" table (e.g. "state:Drupal\Core\Cache\CacheCollector", + // "cron") during rebuilds. These occur because rapid entity/config + // changes trigger cache collectors and lock acquisitions concurrently. + // Safe to run even on initial install (table may not exist yet). + try { + \Drupal::database()->truncate('semaphore')->execute(); + } + catch (\Exception $e) { + // Ignore if table doesn't exist yet. + } + $entity_type_manager = \Drupal::entityTypeManager(); $storage_node_type = $entity_type_manager->getStorage('node_type'); $storage_role = $entity_type_manager->getStorage('user_role');