Paths: File paths (
shared/,references/,../ln-*) are relative to skills repo root. If not found at CWD, locate this SKILL.md directory and go up one level for repo root. Ifshared/is missing, fetch files via WebFetch fromhttps://raw.githubusercontent.com/levnikolaevich/claude-code-skills/master/skills/{path}.
Resource Lifecycle Auditor (L3 Worker)
Type: 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 contextStore with: tech_stack, best_practices, db_config (database type, ORM settings, pool config, session factory), codebase_root, output_dir.
Domain-aware: Supports domain_mode + current_domain.
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
Depends(), Django middleware, Spring@Autowired/@PersistenceContext, Express middleware, Go wire/fx
- FastAPI
-
Discover resource infrastructure
- Find session/connection factory patterns (
sessionmaker,create_engine,DataSource, pool creation) - Find DI registration (
Depends(),@Inject, providers, middleware mounting) - Find streaming endpoints (SSE, WebSocket, long-poll, streaming response)
- Map: which endpoints receive which resources via DI
- Find session/connection factory patterns (
-
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.mdin 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
- Grep:
- Step 2 - Measure session usage span within endpoint body:
- Count lines between first and last
session\.|db\.|await.*repousage - Count total lines in endpoint function body
- Count lines between first and last
- 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
req.dborreq.knexat request start - Grep:
app\.use.*pool|app\.use.*knex|app\.use.*prisma(middleware injection) - Route handler uses
req.dbonly in first 20% of function body
Detection (Java/Spring):
@Transactionalon method with long non-DB processingEntityManagerinjected but used only briefly- Grep:
@Autowired.*EntityManager|@PersistenceContext+ method body analysis
Detection (Go):
sql.DBor*gorm.DBpassed 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)
- Grep:
- Step 2 - Check if streaming function/generator has DB session in scope:
- Session from
Depends()in endpoint signature -> held for entire stream - Session from context manager inside generator -> scoped (OK)
- Session from
- 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
@Transactionalwraps streaming method
Detection (Go):
- Grep:
Flusher|http\.Flusher|websocket\.Conn - Check if
*sql.DBor 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 insidewithorasync with - Grep:
connection\s*=\s*pool\.acquire\(\)|conn\s*=\s*await.*connect\(\)NOT followed bytry:.*finally:.*close\(\) - Pattern: bare
session = get_session()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.release()or.end()in same function - Grep:
createConnection\(\)without.destroy()in try/finally
Detection (Java):
- Grep:
getConnection\(\)|dataSource\.getConnection\(\)without try-with-resources - Pattern:
Connection conn = ds.getConnection()withouttry (Connection conn = ...)syntax
Detection (Go):
- Grep:
sql\.Open\(|db\.Begin\(\)withoutdefer.*Close\(\)|defer.*Rollback\(\) - Pattern:
tx, err := db.Begin()withoutdefer tx.Rollback()
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 / pool_size 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
pool_pre_ping=True-> stale connections not detected - Missing
pool_recycle-> connections kept beyond DB server timeout (default: MySQL 8h, PG unlimited) - Missing
pool_size-> uses default 5 (may be too small for production) - Missing
max_overflow-> unbounded overflow under load pool_size=0orNullPoolin web service -> no pooling (anti-pattern)
- Missing
- 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
- Missing
Detection (Node.js):
- Grep for
createPool\(|new Pool\(:- Missing
min/maxconfiguration - Missing
idleTimeoutMillisorreapIntervalMillis - Missing connection validation (
validateConnection,testOnBorrow)
- Missing
Detection (Java/Spring):
- Grep:
DataSource|HikariConfig|HikariDataSource:- Missing
leakDetectionThreshold - Missing
maximumPoolSize(defaults to 10) - Missing
connectionTestQueryorconnectionInitSql
- Missing
Detection (Go):
- Grep:
sql\.Open\(:- Missing
db.SetMaxOpenConns() - Missing
db.SetMaxIdleConns() - Missing
db.SetConnMaxLifetime()
- Missing
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
exceptblocks containingraiseorreturnwithout priorsession.close(),conn.close(), orcursor.close() - 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.*sessionwithouttry:.*finally:.*close\(\)
- Grep:
Detection (Node.js):
- Grep:
catch\s*\(blocks thatthroworreturnwithout releasing connection - Pattern:
pool.connect().then(client => { ... })without.finally(() => client.release()) - Promise chains without
.finally()for cleanup
Detection (Java):
- Grep:
catch\s*\(blocks withoutfinally { conn.close() }when connection opened intry - Not using try-with-resources for AutoCloseable resources
Detection (Go):
- Grep:
if err != nil \{.*returnbeforedeferstatement for resource cleanup - Pattern: error check between
Open()anddefer Close()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\)
- Grep:
- Step 2 - Classify endpoint lifetime:
- Short-lived: regular REST endpoint (request/response) -> DI injection OK
- Long-lived: SSE (
StreamingResponse,EventSourceResponse), WebSocket (@app.websocket), background task (BackgroundTasks.add_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
- Long-lived endpoint should use factory pattern:
Detection (Node.js/Express):
- Middleware-injected pool connection (
req.db) 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):
@Autowired EntityManagerin@Controllerwith SSE endpoint (SseEmitter)- Should use: programmatic EntityManager creation from EntityManagerFactory
Detection (Go):
*sql.DBinjected at handler construction time but*sql.Connshould 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.
If summaryArtifactPath is present, write JSON summary per shared/references/audit_summary_contract.md. Compact text output is fallback only.
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 per shared/references/audit_summary_contract.md.
Legacy compact text output is allowed only when summaryArtifactPath is absent:
Report written: .hex-skills/runtime-artifacts/runs/{run_id}/audit-report/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
async with,with, try-with-resources,defer(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 written per contract
Reference Files
- Audit output schema:
shared/references/audit_output_schema.md
Version: 1.0.0 Last Updated: 2026-03-03