Claude-code-skills ln-654-resource-lifecycle-auditor
Checks session scope mismatch, missing cleanup, pool config, error path leaks, resource holding. Use when auditing resource lifecycle.
git clone https://github.com/levnikolaevich/claude-code-skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/levnikolaevich/claude-code-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills-catalog/ln-654-resource-lifecycle-auditor" ~/.claude/skills/levnikolaevich-claude-code-skills-ln-654-resource-lifecycle-auditor && rm -rf "$T"
skills-catalog/ln-654-resource-lifecycle-auditor/SKILL.mdPaths: File paths (
,shared/,references/) are relative to skills repo root. If not found at CWD, locate this SKILL.md directory and go up one level for repo root. If../ln-*is missing, fetch files via WebFetch fromshared/.https://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
- 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
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
, Django middleware, SpringDepends()
/@Autowired
, Express middleware, Go wire/fx@PersistenceContext
- FastAPI
-
Discover resource infrastructure
- Find session/connection factory patterns (
,sessionmaker
,create_engine
, pool creation)DataSource - Find DI registration (
,Depends()
, providers, middleware mounting)@Inject - 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
, write toshared/templates/audit_worker_report_template.md
in single Write call{output_dir}/ln-654--global.md -
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
usagesession\.|db\.|await.*repo - Count total lines in endpoint function body
- Count lines between first and last
- Step 3 - Flag if
(session used in <20% of function body)usage_lines / total_lines < 0.2- Especially: session used only at function start (auth check, initial load) but function continues with non-DB work
Detection (Node.js/Express):
- Middleware injects
orreq.db
at request startreq.knex - Grep:
(middleware injection)app\.use.*pool|app\.use.*knex|app\.use.*prisma - Route handler uses
only in first 20% of function bodyreq.db
Detection (Java/Spring):
on method with long non-DB processing@Transactional
injected but used only brieflyEntityManager- Grep:
+ method body analysis@Autowired.*EntityManager|@PersistenceContext
Detection (Go):
orsql.DB
passed to handler, used once, then long processing*gorm.DB- 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:
(SSE generator pattern)yield\s+.*event|yield\s+.*data:|async for.*yield
- Grep:
- Step 2 - Check if streaming function/generator has DB session in scope:
- Session from
in endpoint signature -> held for entire streamDepends() - 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
wraps streaming method@Transactional
Detection (Go):
- Grep:
Flusher|http\.Flusher|websocket\.Conn - Check if
or transaction held during flush loop*sql.DB
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:
NOT insidesession\s*=\s*Session\(\)|session\s*=\s*sessionmaker|engine\.connect\(\)
orwithasync with - Grep:
NOT followed byconnection\s*=\s*pool\.acquire\(\)|conn\s*=\s*await.*connect\(\)try:.*finally:.*close\(\) - Pattern: bare
without context managersession = get_session() - Safe patterns to exclude:
,async with session_factory() as session:with engine.connect() as conn:
Detection (Node.js):
- Grep:
without correspondingpool\.connect\(\)|knex\.client\.acquireConnection|\.getConnection\(\)
or.release()
in same function.end() - Grep:
withoutcreateConnection\(\)
in try/finally.destroy()
Detection (Java):
- Grep:
without try-with-resourcesgetConnection\(\)|dataSource\.getConnection\(\) - Pattern:
withoutConnection conn = ds.getConnection()
syntaxtry (Connection conn = ...)
Detection (Go):
- Grep:
withoutsql\.Open\(|db\.Begin\(\)defer.*Close\(\)|defer.*Rollback\(\) - Pattern:
withouttx, err := db.Begin()defer 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
-> stale connections not detectedpool_pre_ping=True - Missing
-> connections kept beyond DB server timeout (default: MySQL 8h, PG unlimited)pool_recycle - Missing
-> uses default 5 (may be too small for production)pool_size - Missing
-> unbounded overflow under loadmax_overflow
orpool_size=0
in web service -> no pooling (anti-pattern)NullPool
- Missing
- Grep for pool event listeners:
- Missing
-> no visibility into connection invalidation@event.listens_for(engine, "invalidate") - Missing
-> no connection checkout monitoring@event.listens_for(engine, "checkout") - Missing
-> no connection return monitoring@event.listens_for(engine, "checkin")
- Missing
Detection (Node.js):
- Grep for
:createPool\(|new Pool\(- Missing
/min
configurationmax - Missing
oridleTimeoutMillisreapIntervalMillis - Missing connection validation (
,validateConnection
)testOnBorrow
- Missing
Detection (Java/Spring):
- Grep:
:DataSource|HikariConfig|HikariDataSource- Missing
leakDetectionThreshold - Missing
(defaults to 10)maximumPoolSize - Missing
orconnectionTestQueryconnectionInitSql
- 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
blocks containingexcept
orraise
without priorreturn
,session.close()
, orconn.close()cursor.close() - Pattern:
(re-raise without cleanup)except Exception: logger.error(...); raise - Find generator functions with DB session where GeneratorExit is not handled:
- Grep:
withoutasync def.*yield.*session|def.*yield.*sessiontry:.*finally:.*close\(\)
- Grep:
Detection (Node.js):
- Grep:
blocks thatcatch\s*\(
orthrow
without releasing connectionreturn - Pattern:
withoutpool.connect().then(client => { ... }).finally(() => client.release()) - Promise chains without
for cleanup.finally()
Detection (Java):
- Grep:
blocks withoutcatch\s*\(
when connection opened infinally { conn.close() }try - Not using try-with-resources for AutoCloseable resources
Detection (Go):
- Grep:
beforeif err != nil \{.*return
statement for resource cleanupdefer - Pattern: error check between
andOpen()
that returns without closingdefer Close()
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
), WebSocket (EventSourceResponse
), background task (@app.websocket
)BackgroundTasks.add_task
- Step 3 - Flag DI injection in long-lived endpoints:
- Long-lived endpoint should use factory pattern:
at point of needasync with session_factory() as session: - NOT
at endpoint levelsession: AsyncSession = Depends(get_session)
- Long-lived endpoint should use factory pattern:
Detection (Node.js/Express):
- Middleware-injected pool connection (
) used in WebSocket handler or SSE routereq.db - Should use:
at point of needconst conn = await pool.connect(); try { ... } finally { conn.release() }
Detection (Java/Spring):
in@Autowired EntityManager
with SSE endpoint (@Controller
)SseEmitter- Should use: programmatic EntityManager creation from EntityManagerFactory
Detection (Go):
injected at handler construction time but*sql.DB
should be acquired per-operation*sql.Conn
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 JSON summary per
shared/references/audit_summary_contract.md. In managed mode the caller passes both runId and summaryArtifactPath; in standalone mode the worker generates its own run-scoped artifact path per shared contract.
Write report to
{output_dir}/ln-654--global.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.
When
summaryArtifactPath is absent, write the standalone runtime summary under .hex-skills/runtime-artifacts/runs/{run_id}/evaluation-worker/{worker}--{identifier}.json and optionally echo the same summary in structured output.
Report written: .hex-skills/runtime-artifacts/runs/{run_id}/audit-report/ln-654--global.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
, try-with-resources,with
(already managed)defer
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
(atomic single Write call){output_dir}/ln-654--global.md - Summary written per contract
Reference Files
- Audit output schema:
shared/references/audit_output_schema.md
Version: 1.0.0 Last Updated: 2026-03-03