Paths: File paths (
,
,
) are relative to skills repo root. If not found at CWD, locate this SKILL.md directory and go up one level for repo root.
Resource Lifecycle Auditor (L3 Worker)
Specialized worker auditing resource acquisition/release patterns, scope mismatches, and connection pool hygiene.
Purpose & Scope
- Worker in ln-650 coordinator pipeline - invoked by ln-650-persistence-performance-auditor
- Audit resource lifecycle (Priority: HIGH)
- Check session/connection scope mismatch, streaming endpoint resource holding, cleanup patterns, pool config
- Write structured findings to file with severity, location, effort, recommendations
- Calculate compliance score (X/10) for Resource Lifecycle category
Inputs (from Coordinator)
MANDATORY READ: Load
shared/references/audit_worker_core_contract.md
.
Receives
with:
,
,
(database type, ORM settings, pool config, session factory),
,
.
Domain-aware: Supports
+
.
Workflow
MANDATORY READ: Load
shared/references/two_layer_detection.md
for detection methodology.
-
Parse context from contextStore
- Extract tech_stack, best_practices, db_config, output_dir
- Determine scan_path
-
Detect DI framework
- FastAPI , Django middleware, Spring /, Express middleware, Go wire/fx
-
Discover resource infrastructure
- Find session/connection factory patterns (, , , pool creation)
- Find DI registration (, , providers, middleware mounting)
- Find streaming endpoints (SSE, WebSocket, long-poll, streaming response)
- Map: which endpoints receive which resources via DI
-
Scan codebase for violations (6 checks)
- Trace resource injection -> usage -> release across endpoint lifetime
- Analyze streaming endpoints for held resources
- Check error paths for cleanup
-
Collect findings with severity, location, effort, recommendation
-
Calculate score using penalty algorithm
-
Write Report: Build full markdown report in memory per
shared/templates/audit_worker_report_template.md
, write to
{output_dir}/654-resource-lifecycle.md
in single Write call
-
Return Summary: Return minimal summary to coordinator (see Output Format)
Audit Rules (Priority: HIGH)
1. Resource Scope Mismatch
What: Resource injected via DI lives for entire request/connection scope but is used for only a fraction of it.
Detection (Python/FastAPI):
- Step 1 - Find endpoints with DB session dependency:
- Grep:
async def\s+\w+\(.*Depends\(get_db\)|Depends\(get_session\)|db:\s*AsyncSession|session:\s*AsyncSession
- Step 2 - Measure session usage span within endpoint body:
- Count lines between first and last
session\.|db\.|await.*repo
usage
- Count total lines in endpoint function body
- Step 3 - Flag if
usage_lines / total_lines < 0.2
(session used in <20% of function body)
- Especially: session used only at function start (auth check, initial load) but function continues with non-DB work
Detection (Node.js/Express):
- Middleware injects or at request start
- Grep:
app\.use.*pool|app\.use.*knex|app\.use.*prisma
(middleware injection)
- Route handler uses only in first 20% of function body
Detection (Java/Spring):
- on method with long non-DB processing
- injected but used only briefly
- Grep:
@Autowired.*EntityManager|@PersistenceContext
+ method body analysis
Detection (Go):
- or passed to handler, used once, then long processing
- Grep:
func.*Handler.*\*sql\.DB|func.*Handler.*\*gorm\.DB
Severity:
- CRITICAL: Session scope mismatch in streaming endpoint (SSE, WebSocket) - session held for minutes/hours
- HIGH: Session scope mismatch in endpoint with external API calls (session held during network latency)
- MEDIUM: Session scope mismatch in endpoint with >50 lines of non-DB processing
Recommendation: Extract DB operations into scoped function; acquire session only for the duration needed; use
async with get_session() as session:
block instead of endpoint-level DI injection.
Effort: M (refactor DI to scoped acquisition)
2. Streaming Endpoint Resource Holding
What: SSE, WebSocket, or long-poll endpoint holds DB session/connection for stream duration.
Detection (Python/FastAPI):
- Step 1 - Find streaming endpoints:
- Grep:
StreamingResponse|EventSourceResponse|SSE|async def.*websocket|@app\.websocket
- Grep:
yield\s+.*event|yield\s+.*data:|async for.*yield
(SSE generator pattern)
- Step 2 - Check if streaming function/generator has DB session in scope:
- Session from in endpoint signature -> held for entire stream
- Session from context manager inside generator -> scoped (OK)
- Step 3 - Analyze session usage inside generator:
- If session used once at start (auth/permission check) then stream loops without DB -> scope mismatch
Detection (Node.js):
- Grep:
res\.write\(|res\.flush\(|Server-Sent Events|new WebSocket|ws\.on\(
- Check if connection/pool client acquired before stream loop and not released
Detection (Java/Spring):
- Grep:
SseEmitter|WebSocketHandler|StreamingResponseBody
- Check if wraps streaming method
Detection (Go):
- Grep:
Flusher|http\.Flusher|websocket\.Conn
- Check if or transaction held during flush loop
Severity:
- CRITICAL: DB session/connection held for entire SSE/WebSocket stream duration (pool exhaustion under load)
- HIGH: DB connection held during long-poll (>30s timeout)
Recommendation: Move auth/permission check BEFORE stream: acquire session, check auth, release session, THEN start streaming. Use separate scoped session for any mid-stream DB access.
Effort: M (restructure endpoint to release session before streaming)
3. Missing Resource Cleanup Patterns
What: Resource acquired without guaranteed cleanup (no try/finally, no context manager, no close()).
Detection (Python):
- Grep:
session\s*=\s*Session\(\)|session\s*=\s*sessionmaker|engine\.connect\(\)
NOT inside or
- Grep:
connection\s*=\s*pool\.acquire\(\)|conn\s*=\s*await.*connect\(\)
NOT followed by try:.*finally:.*close\(\)
- Pattern: bare without context manager
- Safe patterns to exclude:
async with session_factory() as session:
, with engine.connect() as conn:
Detection (Node.js):
- Grep:
pool\.connect\(\)|knex\.client\.acquireConnection|\.getConnection\(\)
without corresponding or in same function
- Grep: without in try/finally
Detection (Java):
- Grep:
getConnection\(\)|dataSource\.getConnection\(\)
without try-with-resources
- Pattern:
Connection conn = ds.getConnection()
without try (Connection conn = ...)
syntax
Detection (Go):
- Grep:
sql\.Open\(|db\.Begin\(\)
without defer.*Close\(\)|defer.*Rollback\(\)
- Pattern: without
Severity:
- HIGH: Session/connection acquired without cleanup guarantee (leak on exception)
- MEDIUM: File handle or cursor without cleanup in non-critical path
Exception: Session acquired and released before streaming/long-poll begins → skip. NullPool /
config documented as serverless design → skip.
Recommendation: Ensure resources are cleaned up on all exit paths (context managers, try-finally, or framework-managed lifecycle).
Effort: S (wrap in context manager or add defer)
4. Connection Pool Configuration Gaps
What: Missing pool health monitoring, no pre-ping, no recycle, no overflow limits.
Detection (Python/SQLAlchemy):
- Grep for
create_engine\(|create_async_engine\(
:
- Missing -> stale connections not detected
- Missing -> connections kept beyond DB server timeout (default: MySQL 8h, PG unlimited)
- Missing -> uses default 5 (may be too small for production)
- Missing -> unbounded overflow under load
- or in web service -> no pooling (anti-pattern)
- Grep for pool event listeners:
- Missing
@event.listens_for(engine, "invalidate")
-> no visibility into connection invalidation
- Missing
@event.listens_for(engine, "checkout")
-> no connection checkout monitoring
- Missing
@event.listens_for(engine, "checkin")
-> no connection return monitoring
Detection (Node.js):
- Grep for :
- Missing / configuration
- Missing or
- Missing connection validation (, )
Detection (Java/Spring):
- Grep:
DataSource|HikariConfig|HikariDataSource
:
- Missing
- Missing (defaults to 10)
- Missing or
Detection (Go):
Severity:
- HIGH: No pool_pre_ping AND no pool_recycle (stale connections served silently)
- HIGH: No max_overflow limit in web service (unbounded connection creation under load)
- MEDIUM: Missing pool event listeners (no visibility into pool health)
- MEDIUM: Missing leak detection threshold (Java/HikariCP)
- LOW: Pool size at default value (may be adequate for small services)
Context-dependent exceptions:
- NullPool is valid for serverless/Lambda
- pool_size=5 may be fine for low-traffic services
Recommendation: Configure pool_pre_ping=True, pool_recycle < DB server timeout, appropriate pool_size for expected concurrency, add pool event listeners for monitoring.
Effort: S (add config parameters), M (add event listeners/monitoring)
5. Unclosed Resources in Error Paths
What: Exception/error handling paths that skip resource cleanup.
Detection (Python):
- Find blocks containing or without prior , , or
- Pattern:
except Exception: logger.error(...); raise
(re-raise without cleanup)
- Find generator functions with DB session where GeneratorExit is not handled:
- Grep:
async def.*yield.*session|def.*yield.*session
without try:.*finally:.*close\(\)
Detection (Node.js):
- Grep: blocks that or without releasing connection
- Pattern:
pool.connect().then(client => { ... })
without .finally(() => client.release())
- Promise chains without for cleanup
Detection (Java):
- Grep: blocks without when connection opened in
- Not using try-with-resources for AutoCloseable resources
Detection (Go):
- Grep: before statement for resource cleanup
- Pattern: error check between and that returns without closing
Severity:
- CRITICAL: Session/connection leak in high-frequency endpoint error path (pool exhaustion)
- HIGH: Resource leak in error path of API handler
- MEDIUM: Resource leak in error path of background task
Recommendation: Use context managers/try-with-resources/defer BEFORE any code that can fail; for generators, add try/finally around yield.
Effort: S (restructure acquisition to before-error-path)
6. Resource Factory vs Injection Anti-pattern
What: Using framework DI to inject short-lived resources into long-lived contexts instead of using factory pattern.
Detection (Python/FastAPI):
- Step 1 - Find DI-injected sessions in endpoint signatures:
- Grep:
Depends\(get_db\)|Depends\(get_session\)|Depends\(get_async_session\)
- Step 2 - Classify endpoint lifetime:
- Short-lived: regular REST endpoint (request/response) -> DI injection OK
- Long-lived: SSE (, ), WebSocket (), background task ()
- Step 3 - Flag DI injection in long-lived endpoints:
- Long-lived endpoint should use factory pattern:
async with session_factory() as session:
at point of need
- NOT
session: AsyncSession = Depends(get_session)
at endpoint level
Detection (Node.js/Express):
- Middleware-injected pool connection () used in WebSocket handler or SSE route
- Should use:
const conn = await pool.connect(); try { ... } finally { conn.release() }
at point of need
Detection (Java/Spring):
- in with SSE endpoint ()
- Should use: programmatic EntityManager creation from EntityManagerFactory
Detection (Go):
- injected at handler construction time but should be acquired per-operation
Severity:
- CRITICAL: DI-injected session in SSE/WebSocket endpoint (session outlives intended scope by orders of magnitude)
- HIGH: DI-injected session passed to background task (task outlives request)
Recommendation: Use factory pattern for long-lived contexts; inject the factory (sessionmaker, pool), not the session/connection itself.
Effort: M (change DI from session to session factory, add scoped acquisition)
Scoring Algorithm
MANDATORY READ: Load
shared/references/audit_worker_core_contract.md
and
shared/references/audit_scoring.md
.
Output Format
MANDATORY READ: Load
shared/references/audit_worker_core_contract.md
and
shared/templates/audit_worker_report_template.md
.
Write report to
{output_dir}/654-resource-lifecycle.md
with
category: "Resource Lifecycle"
and checks: resource_scope_mismatch, streaming_resource_holding, missing_cleanup, pool_configuration, error_path_leak, factory_vs_injection.
Return summary to coordinator:
Report written: docs/project/.audit/ln-650/{YYYY-MM-DD}/654-resource-lifecycle.md
Score: X.X/10 | Issues: N (C:N H:N M:N L:N)
Critical Rules
MANDATORY READ: Load
shared/references/audit_worker_core_contract.md
.
- Do not auto-fix: Report only
- DI-aware: Understand framework dependency injection lifetime scopes (request, singleton, transient)
- Framework detection first: Identify DI framework before checking injection patterns
- Streaming detection first: Find all streaming/long-lived endpoints before scope analysis
- Exclude tests: Do not flag test fixtures, test session setup, mock sessions
- Exclude CLI/scripts: DI scope mismatch is not relevant for single-run scripts
- Effort realism: S = <1h, M = 1-4h, L = >4h
- Pool config is context-dependent: NullPool is valid for serverless/Lambda; pool_size=5 may be fine for low-traffic services
- Safe pattern awareness: Do not flag resources inside , , try-with-resources, (already managed)
Definition of Done
MANDATORY READ: Load
shared/references/audit_worker_core_contract.md
.
- contextStore parsed successfully (including output_dir, db_config)
- scan_path determined
- DI framework detected (FastAPI Depends, Django middleware, Spring @Autowired, Express middleware, Go wire)
- Streaming endpoints inventoried
- All 6 checks completed:
- resource scope mismatch, streaming resource holding, missing cleanup, pool configuration, error path leak, factory vs injection
- Findings collected with severity, location, effort, recommendation
- Score calculated using penalty algorithm
- Report written to
{output_dir}/654-resource-lifecycle.md
(atomic single Write call)
- Summary returned to coordinator
Reference Files
- Audit output schema:
shared/references/audit_output_schema.md
Version: 1.0.0
Last Updated: 2026-03-03