-
Notifications
You must be signed in to change notification settings - Fork 0
chore: migrate to uv package manager and optimize production deployments #74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
02e3a4d
6924bb9
6c88be5
fd41c6e
c235058
98afc6e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| set -e | ||
| echo "==> Navigating to project directory..." | ||
| cd ~/apps/spoken-api | ||
|
|
||
| echo "==> Running Alembic migrations..." | ||
| # Use -T for non-interactive shell in CI | ||
| docker compose -f docker-compose.prod.yml exec -T api \ | ||
| alembic -x sqlalchemy.url="${DATABASE_URL_DIRECT}" upgrade head | ||
| echo "==> Migrations complete." | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| set -e | ||
|
|
||
| echo "==> Navigating to project directory..." | ||
| cd ~/apps/spoken-api | ||
|
|
||
| echo "==> Pulling latest code from main..." | ||
| git pull origin main | ||
|
Comment on lines
+1
to
+7
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make the deploy script deterministic and shell-safe. For deployment automation, add a shebang/strict mode and replace Proposed fix+#!/usr/bin/env bash
-set -e
+set -euo pipefail
+
+git fetch origin main
+git reset --hard origin/main
-
- echo "==> Pulling latest code from main..."
- git pull origin main
+ echo "==> Synced to origin/main."🤖 Prompt for AI Agents |
||
|
|
||
| echo "==> Rebuilding API container..." | ||
| docker compose -f docker-compose.prod.yml build api | ||
|
|
||
| echo "==> Restarting API container..." | ||
| docker compose -f docker-compose.prod.yml up -d api | ||
|
|
||
| echo "==> Waiting for container to be healthy (up to 90s)..." | ||
| ITER=0 | ||
| while [ $ITER -lt 12 ]; do | ||
|
Comment on lines
+15
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Health-wait window is inconsistent with the printed timeout. The script says “up to 90s” but Proposed fix- while [ $ITER -lt 12 ]; do
+ while [ $ITER -lt 18 ]; do🤖 Prompt for AI Agents |
||
| STATUS=$(docker inspect --format='{{.State.Health.Status}}' spoken-api 2>/dev/null || echo "starting") | ||
| echo "==> Current status: $STATUS" | ||
| if [ "$STATUS" = "healthy" ]; then | ||
| break | ||
| fi | ||
| sleep 5 | ||
| ITER=$((ITER + 1)) | ||
| done | ||
|
|
||
| STATUS=$(docker inspect --format='{{.State.Health.Status}}' spoken-api 2>/dev/null || echo "unknown") | ||
| echo "==> Container health status: $STATUS" | ||
| if [ "$STATUS" != "healthy" ]; then | ||
| echo "ERROR: Container did not become healthy in time!" | ||
| docker compose -f docker-compose.prod.yml logs --tail=50 api | ||
| exit 1 | ||
| fi | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,24 +11,26 @@ jobs: | |
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - name: Set up Python 3.11 | ||
| - name: Install uv | ||
| uses: astral-sh/setup-uv@v5 | ||
| with: | ||
| enable-cache: true | ||
| cache-dependency-glob: "uv.lock" | ||
| - name: Set up Python | ||
| uses: actions/setup-python@v5 | ||
| with: | ||
| python-version: '3.11' | ||
| cache: 'pip' | ||
| python-version-file: "pyproject.toml" | ||
| - name: Install dependencies | ||
|
Comment on lines
+14
to
23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pin GitHub Actions to immutable commit SHAs. 🧰 Tools🪛 zizmor (1.25.2)[error] 15-15: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) [error] 20-20: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) 🤖 Prompt for AI Agents |
||
| run: | | ||
| python -m pip install --upgrade pip | ||
| pip install -r requirements.txt | ||
| run: uv sync --frozen | ||
| - name: Lint with Black | ||
| run: black --check . | ||
| run: uv run black --check . | ||
| - name: Check imports with isort | ||
| run: isort --check-only . | ||
| run: uv run isort --check-only . | ||
| - name: Type check with Mypy | ||
| run: mypy app | ||
| run: uv run mypy app | ||
| - name: Run tests with Pytest | ||
| env: | ||
| DATABASE_URL: postgresql://postgres:postgres@localhost:5432/fluentmeet_test | ||
| REDIS_URL: redis://localhost:6379/1 | ||
| run: | | ||
| pytest --cov=app --cov-fail-under=77 tests/ | ||
| uv run pytest --cov=app --cov-fail-under=77 tests/ | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,20 +12,20 @@ jobs: | |
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - name: Set up Python 3.11 | ||
| - name: Install uv | ||
| uses: astral-sh/setup-uv@v5 | ||
| with: | ||
| enable-cache: true | ||
| cache-dependency-glob: "uv.lock" | ||
| - name: Set up Python | ||
| uses: actions/setup-python@v5 | ||
| with: | ||
| python-version: '3.11' | ||
| cache: 'pip' | ||
| python-version-file: "pyproject.toml" | ||
|
Comment on lines
+15
to
+23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pin GitHub Actions to immutable commit SHAs (not mutable 🧰 Tools🪛 zizmor (1.25.2)[error] 16-16: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) [error] 21-21: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) 🤖 Prompt for AI Agents |
||
| - name: Install dependencies | ||
| run: | | ||
| python -m pip install --upgrade pip | ||
| pip install ruff mypy | ||
| # Install project deps to help mypy find types | ||
| pip install -r requirements.txt | ||
| run: uv sync --frozen | ||
| - name: Lint with Ruff | ||
| run: ruff check . | ||
| run: uv run ruff check . | ||
| - name: Format check with Ruff | ||
| run: ruff format --check . | ||
| run: uv run ruff format --check . | ||
| - name: Type check with Mypy | ||
| run: mypy app | ||
| run: uv run mypy app | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,13 +5,17 @@ FROM python:3.11-slim AS builder | |
|
|
||
| WORKDIR /build | ||
|
|
||
| # Copy uv binary from the official image | ||
| COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# Show Dockerfile around the referenced lines
if [ -f Dockerfile ]; then
echo "== Dockerfile (lines 1-40) =="
nl -ba Dockerfile | sed -n '1,40p'
else
echo "Dockerfile not found at repo root"
exit 1
fi
# Find all occurrences of the uv image/tag
echo "== Search for ghcr.io/astral-sh/uv =="
rg -n "ghcr\.io/astral-sh/uv(?::[^ \t]+)?" . || true
echo "== Search for :latest usage =="
rg -n "ghcr\.io/astral-sh/uv:latest" . || trueRepository: Brints/spoken-api Length of output: 152 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Show Dockerfile around the referenced lines
if [ -f Dockerfile ]; then
echo "== Dockerfile (lines 1-60) =="
cat -n Dockerfile | sed -n '1,60p'
else
echo "Dockerfile not found at repo root"
exit 1
fi
echo "== Search for ghcr.io/astral-sh/uv =="
rg -n "ghcr\.io/astral-sh/uv" . || true
echo "== Search for ghcr.io/astral-sh/uv:latest specifically =="
rg -n "ghcr\.io/astral-sh/uv:latest" . || true
echo "== Search for any COPY --from=ghcr.io/astral-sh/uv:... =="
rg -n "COPY\s+--from=ghcr\.io/astral-sh/uv:[^\s]+" Dockerfile || trueRepository: Brints/spoken-api Length of output: 2608 🌐 Web query:
💡 Result: ghcr.io/astral-sh/uv:latest tag is intended as a “floating”/convenience tag, so it should be treated as mutable; for reproducibility you should pin by image digest (or at least by a specific version tag like:0.11.16). The uv Docker docs explicitly recommend pinning to a specific uv version and state that pinning to a specific SHA256 digest is best practice, because tags can be moved across different commit SHAs [1]. Practical ways to “digest pin”: 1) Pin by digest directly in Dockerfile/COPY, e.g. replace:latest with ghcr.io/astral-sh/uv@sha256: (docs show this pattern for the image) [1]. 2) Derive the digest for:latest at build/CI time using a registry query tool, then use that digest for the actual copy/deploy step (the docs show Citations: Pin COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/🤖 Prompt for AI Agents |
||
|
|
||
| # System deps needed to compile psycopg2, cryptography, etc. | ||
| RUN apt-get update && \ | ||
| apt-get install -y --no-install-recommends gcc libpq-dev && \ | ||
| rm -rf /var/lib/apt/lists/* | ||
|
|
||
| COPY requirements.txt . | ||
| RUN pip install --no-cache-dir --prefix=/install -r requirements.txt | ||
| # Sync dependencies to a virtual environment | ||
| COPY pyproject.toml uv.lock ./ | ||
| RUN uv sync --frozen --no-dev | ||
|
|
||
| # ============================================================ | ||
| # Stage 2 — Runtime: lean production image | ||
|
|
@@ -28,8 +32,8 @@ RUN groupadd -r appuser && useradd -r -g appuser appuser | |
|
|
||
| WORKDIR /app | ||
|
|
||
| # Copy installed packages from builder | ||
| COPY --from=builder /install /usr/local | ||
| # Copy the virtual environment from the builder | ||
| COPY --from=builder /build/.venv /app/.venv | ||
|
|
||
| # Copy application source | ||
| COPY pyproject.toml ./ | ||
|
|
@@ -47,5 +51,8 @@ EXPOSE 8000 | |
| HEALTHCHECK --interval=30s --timeout=5s --retries=3 \ | ||
| CMD curl -f http://localhost:8000/health || exit 1 | ||
|
|
||
| # Run using the python interpreter inside the virtual environment | ||
| ENV PATH="/app/.venv/bin:$PATH" | ||
|
|
||
| # Run with uvicorn — 4 workers for production | ||
| CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"] | ||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -83,12 +83,10 @@ Generate a secure `SECRET_KEY` for JWT: | |||||||
| python -c "import secrets; print(secrets.token_hex(32))" | ||||||||
| ``` | ||||||||
|
|
||||||||
| ### **4. Local Development Setup** | ||||||||
| It is highly recommended to use a virtual environment: | ||||||||
| ### 4. Local Development Setup | ||||||||
| It is highly recommended to use the `uv` tool for fast virtual environment and dependency management: | ||||||||
| ```bash | ||||||||
| python -m venv .venv | ||||||||
| source .venv/bin/activate # On Windows: .venv\Scripts\activate | ||||||||
| pip install -r requirements.txt | ||||||||
| uv sync | ||||||||
| ``` | ||||||||
|
Comment on lines
+86
to
90
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# Show README.md around the reported lines
echo "---- README.md: lines 70-120 ----"
nl -ba README.md | sed -n '70,120p'
# Find all mentions of uv
echo "---- rg 'uv' in README.md ----"
rg -n "(\buv\b)" README.md || true
# Look for any install/setup instructions for uv
echo "---- rg install/setup keywords for uv ----"
rg -n "(install|setup|prerequisite|requires|tooling).*uv|uv.*(install|setup|prerequisite)|curl.*uv|brew.*uv|pipx.*uv" README.md || trueRepository: Brints/spoken-api Length of output: 156 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "---- README.md: lines 70-120 ----"
awk 'NR>=70 && NR<=120 {printf "%d\t%s\n", NR, $0}' README.md
echo "---- rg 'uv' in README.md ----"
rg -n "(\buv\b)" README.md || true
echo "---- rg install/setup keywords around uv ----"
rg -n "(install|setup|prerequisite|requires|tooling).{0,40}uv|uv.{0,40}(install|setup|prerequisite)|curl.{0,40}uv|brew.{0,40}uv|pipx.{0,40}uv" README.md || trueRepository: Brints/spoken-api Length of output: 2144 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "---- README.md: lines 70-120 ----"
awk 'NR>=70 && NR<=120 {printf "%d\t%s\n", NR, $0}' README.md
echo "---- rg 'uv' in README.md ----"
rg -n "(\buv\b)" README.md || true
echo "---- rg install/setup keywords around uv ----"
rg -n "(install|setup|prerequisite|requires|tooling).{0,40}uv|uv.{0,40}(install|setup|prerequisite)|curl.{0,40}uv|brew.{0,40}uv|pipx.{0,40}uv" README.md || trueRepository: Brints/spoken-api Length of output: 2144 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "---- README.md: lines 1-120 ----"
awk 'NR>=1 && NR<=120 {printf "%d\t%s\n", NR, $0}' README.md
echo "---- README.md: lines 120-210 ----"
awk 'NR>=120 && NR<=210 {printf "%d\t%s\n", NR, $0}' README.md
echo "---- README.md: lines 210-260 ----"
awk 'NR>=210 && NR<=260 {printf "%d\t%s\n", NR, $0}' README.md
echo "---- rg 'uv' in README.md (full match context) ----"
rg -n --context=2 "\buv\b" README.md || trueRepository: Brints/spoken-api Length of output: 9579 Add an explicit “Install uv” step before running The README recommends 🤖 Prompt for AI Agents |
||||||||
|
|
||||||||
| ### **5. Infrastructure Setup (Docker)** | ||||||||
|
|
@@ -109,7 +107,8 @@ alembic upgrade headalembic upgrade head | |||||||
|
|
||||||||
| ### **Start the Backend** | ||||||||
| ```bash | ||||||||
| uvicorn app.main:app --reload | ||||||||
| uv run uvicorn app.main:app --reload | ||||||||
| uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload | ||||||||
|
Comment on lines
+110
to
+111
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove duplicate backend start command. Line 110 and Line 111 document two startup commands for the same purpose; keep one canonical command to avoid confusion. Doc cleanup-uv run uvicorn app.main:app --reload
uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||
| ``` | ||||||||
| The API will be available at `http://localhost:8000`. You can access the interactive API documentation (Swagger UI) at `http://localhost:8000/docs`. | ||||||||
|
|
||||||||
|
|
@@ -126,16 +125,16 @@ Ensure your models are imported in `app/models/init.py` for Alembic to detect th | |||||||
|
|
||||||||
| ### **Creating Migrations** | ||||||||
| ```bash | ||||||||
| python -m alembic revision --autogenerate -m "Add avatar url to users" | ||||||||
| uv run alembic revision --autogenerate -m "Add avatar url to users" | ||||||||
| ``` | ||||||||
| ### **Migrate Head** | ||||||||
| ```bash | ||||||||
| alembic upgrade head | ||||||||
| uv run alembic upgrade head | ||||||||
| ``` | ||||||||
|
|
||||||||
| ### **Applying Migrations** | ||||||||
| ```bash | ||||||||
| python -m alembic upgrade head | ||||||||
| uv run alembic upgrade head | ||||||||
| ``` | ||||||||
|
|
||||||||
| --- | ||||||||
|
|
@@ -144,13 +143,13 @@ python -m alembic upgrade head | |||||||
|
|
||||||||
| ### **Running Tests** | ||||||||
| ```bash | ||||||||
| pytest | ||||||||
| uv run pytest | ||||||||
| ``` | ||||||||
|
|
||||||||
| ### **Test Coverage** | ||||||||
| Generate and view a coverage report: | ||||||||
| ```bash | ||||||||
| pytest tests/ -v --cov=app --cov-report=html --cov-report=term | ||||||||
| uv run pytest tests/ -v --cov=app --cov-report=html --cov-report=term | ||||||||
| # Open htmlcov/index.html in your browser | ||||||||
| ``` | ||||||||
|
|
||||||||
|
|
@@ -168,19 +167,16 @@ pytest tests/ -v --cov=app --cov-report=html --cov-report=term | |||||||
| ## Linting & Formatting | ||||||||
| - **Black**: Enforce consistent code formatting. | ||||||||
| ```bash | ||||||||
| black . | ||||||||
| uv run black . | ||||||||
| ``` | ||||||||
| - **isort**: Sort imports for readability. | ||||||||
| ```bash | ||||||||
| isort . | ||||||||
| uv run isort . | ||||||||
| ``` | ||||||||
| - **ruff**: Linting for code quality and style. | ||||||||
| ```bash | ||||||||
| ruff . | ||||||||
| ``` | ||||||||
| ```bash | ||||||||
| python -m ruff check . | ||||||||
| ruff format . | ||||||||
| uv run ruff check . | ||||||||
| uv run ruff format . | ||||||||
| ``` | ||||||||
|
|
||||||||
| --- | ||||||||
|
|
||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,3 @@ | ||
| version: "3.8" | ||
|
|
||
| services: | ||
| # ── Redis ────────────────────────────────────────────────── | ||
| redis: | ||
|
|
@@ -38,11 +36,11 @@ services: | |
| volumes: | ||
| - kafka_data:/var/lib/kafka/data | ||
| healthcheck: | ||
| test: ["CMD-SHELL", "/opt/kafka/bin/kafka-broker-api-versions.sh --bootstrap-server localhost:29092"] | ||
| interval: 15s | ||
| timeout: 10s | ||
| retries: 5 | ||
| start_period: 30s | ||
| test: ["CMD-SHELL", "/opt/kafka/bin/kafka-broker-api-versions.sh --bootstrap-server 127.0.0.1:29092"] | ||
| interval: 30s | ||
| timeout: 15s | ||
| retries: 10 | ||
| start_period: 90s | ||
| networks: | ||
| - spoken-net | ||
|
|
||
|
|
@@ -63,7 +61,7 @@ services: | |
| redis: | ||
| condition: service_healthy | ||
| kafka: | ||
| condition: service_healthy | ||
| condition: service_started | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Inspect startup-time Kafka usage and retry/backoff handling.
rg -n -C3 --iglob '*.py' '\b(KAFKA_BOOTSTRAP_SERVERS|AIOKafka|KafkaProducer|KafkaConsumer|bootstrap_servers)\b'
rg -n -C3 --iglob '*.py' '\b(retry|backoff|tenacity|sleep\(|wait_for|retrying)\b'Repository: Brints/spoken-api Length of output: 12576 🏁 Script executed: #!/bin/bash
set -euo pipefail
# 1) Confirm depends_on condition at the referenced line
echo "== docker-compose.prod.yml around line 64 =="
nl -ba docker-compose.prod.yml | sed -n '50,80p'
# 2) Inspect API startup/lifespan hook where KafkaManager.start() is called (if present)
echo "== Searching for KafkaManager / get_kafka_manager usage =="
rg -n --hidden --iglob 'app/**/*.py' '\bget_kafka_manager\b|\bKafkaManager\b|kafka_manager\.start\(' app || true
# 3) Inspect KafkaManager.start() and initialization flow
echo "== app/kafka/manager.py relevant sections =="
nl -ba app/kafka/manager.py | sed -n '1,260p'
# 4) Inspect producer/consumer startup methods for retry/backoff
echo "== app/kafka/producer.py relevant sections =="
nl -ba app/kafka/producer.py | sed -n '1,220p'
echo "== app/kafka/consumer.py relevant sections =="
nl -ba app/kafka/consumer.py | sed -n '1,260p'
# 5) Inspect exception handling around Kafka startup in FastAPI/main (if found)
echo "== Searching for FastAPI lifespan / startup blocks =="
rg -n --hidden --iglob 'app/**/*.py' '\blifespan\b|startup\b|on_startup\b' app || true
# If app/main.py exists, show it (only if small enough)
if [ -f app/main.py ]; then
echo "== app/main.py =="
wc -l app/main.py
nl -ba app/main.py | sed -n '1,260p'
fiRepository: Brints/spoken-api Length of output: 167 🏁 Script executed: #!/bin/bash
set -euo pipefail
# helper: print file with line numbers (works without nl)
print_with_numbers() {
local file="$1"
awk '{printf "%6d:%s\n", NR, $0}' "$file"
}
echo "== docker-compose.prod.yml around line 64 =="
if [ -f docker-compose.prod.yml ]; then
print_with_numbers docker-compose.prod.yml | sed -n '50,80p'
else
echo "Missing docker-compose.prod.yml"
fi
echo "== Searching for KafkaManager / get_kafka_manager usage =="
rg -n --hidden --iglob 'app/**/*.py' '\bget_kafka_manager\b|\bKafkaManager\b|kafka_manager\.start\(' app || true
rg -n --hidden --iglob 'app/**/*.py' '\b(lifespan|on_startup|startup)\b' app || true
echo "== app/kafka/manager.py =="
if [ -f app/kafka/manager.py ]; then
wc -l app/kafka/manager.py
# show the start/init path only
rg -n 'class KafkaManager|async def start|_init_topics|AIOKafkaAdminClient' app/kafka/manager.py
print_with_numbers app/kafka/manager.py | sed -n '1,260p'
else
echo "Missing app/kafka/manager.py"
fi
echo "== app/kafka/producer.py =="
if [ -f app/kafka/producer.py ]; then
wc -l app/kafka/producer.py
rg -n 'class KafkaProducer|async def start|def is_started' app/kafka/producer.py
print_with_numbers app/kafka/producer.py | sed -n '1,220p'
else
echo "Missing app/kafka/producer.py"
fi
echo "== app/kafka/consumer.py =="
if [ -f app/kafka/consumer.py ]; then
wc -l app/kafka/consumer.py
rg -n 'class BaseConsumer|async def start|_process_with_retry|async def _process_with_retry' app/kafka/consumer.py
print_with_numbers app/kafka/consumer.py | sed -n '1,320p'
else
echo "Missing app/kafka/consumer.py"
fi
# Inspect FastAPI entrypoint files if present
for f in app/main.py main.py app/app.py server.py; do
if [ -f "$f" ]; then
echo "== Inspecting $f =="
wc -l "$f"
rg -n --hidden '\blifespan\b|on_startup|startup|KafkaManager|get_kafka_manager|kafka_manager\.start\(' "$f" || true
print_with_numbers "$f" | sed -n '1,260p'
fi
doneRepository: Brints/spoken-api Length of output: 31198 Handle Kafka not-ready startup explicitly when using
🤖 Prompt for AI Agents |
||
| ports: | ||
| - "8000:8000" | ||
| healthcheck: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add shebang and strict env validation to prevent unsafe migration runs.
This script currently depends on caller shell semantics and does not fail fast on missing
DATABASE_URL_DIRECT. That can cause execution failure or unintended migration target resolution.Proposed fix
🤖 Prompt for AI Agents