agentleFS
adamentwistleadamentwistle/fable-skills/resource-lifecycle/SKILL.md

resource-lifecycle

Skill98 starsChanged 2 months agoMITView on GitHub

Every acquired resource — connection, file, lock, transaction, subprocess, timer, listener, stream, temp file, cursor — has an owner and a release on every exit path, including errors and cancellation. Use when writing or reviewing code that opens, connects, spawns, subscribes, schedules, locks, begins a transaction, or creates anything with a close/dispose/unsubscribe counterpart, and for bugs described as "slow after a while", "too many open files", "pool exhausted", or "process won't exit".

The file

We split this skill into the rules it gives an agent

1 rule. About 0 are common, 0 are distinctive, and 1 is very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈0

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Check the release runs on every exit: early return, continue, thrown error, and in async code, cancellation and abort (see …

    line 17

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

resource-lifecycle

Weak resource handling opens, uses, and closes on the happy path; the first exception leaks the handle, and the leak surfaces a week later as "pool exhausted" or a process that never exits. Strong handling decides who owns each resource at the moment it is acquired and writes the release before the use.

1. Inventory the acquisitions

Before finishing a diff, grep it for every acquire: open(, connect, spawn / exec / Popen, begin / transaction, lock / acquire, setInterval / setTimeout / schedule, addEventListener / .on( / subscribe, createReadStream / pipe, mkstemp / tmp, cursor, Session(, new Client(. One line per hit: resource → owner → release site. A hit with no release site is a leak until proven otherwise.

2. Use the scoped construct the language gives you

  • Python with, JS/TS try/finally (or using / await using where supported), Go defer on the line after the acquire, Java try-with-resources, Rust drop. The release lives in the construct, not at the bottom of the function.
  • Check the release runs on every exit: early return, continue, thrown error, and in async code, cancellation and abort (see concurrency-reasoning). An AbortSignal, CancelledError, or context cancellation must still reach the release.
  • Release in reverse acquisition order. A release that can itself throw is wrapped so the remaining releases still run.

3. Ownership across boundaries

  • A function that returns an open resource (stream, connection, cursor) hands ownership to the caller. Say so in its name or docstring (open_…, acquire_…) and check every caller closes it. Prefer returning the result and closing inside.
  • A resource held in a field or module global needs a shutdown hook. Find where the project shuts down (signal handler, app.on('close'), atexit, lifespan) and register the release there.
  • Callbacks registered in a lifecycle (React effect, component mount, plugin activate) are unregistered in the matching teardown. Every on has an off; every setInterval has a clearInterval.

4. Pools, transactions, subprocesses, temp files

  • Pools: acquire with a timeout; return in finally; never hold a connection across an await on something unrelated (an external HTTP call). Pool size comes from config, not a literal.
  • Transactions: commit on success, roll back on any exception, and never hold one open across a user or network wait. A "transaction" opened inside another is a savepoint or a bug; check which the library does.
  • Subprocesses: read or redirect both stdout and stderr (an unread pipe fills and deadlocks the child); set a timeout; on timeout kill and then wait so no zombie remains; close the pipes.
  • Temp files and directories: create through the platform API, remove in finally, and never present one as the result.

5. Bound what accumulates

Caches, listener lists, in-memory queues, and pending-promise maps grow forever unless something removes entries. Name the eviction: TTL, max size, or removal on completion. An unbounded Map keyed by request ID is a leak with extra steps (see perf-sanity).

6. Prove it

Exercise the error path once: force an exception after the acquire (bad input, a mocked failure) and observe that the release ran — pool count back to baseline, open-handle count flat, the process exits without process.exit(), the temp dir empty. Then run the operation 100 times in a loop and watch the count. A leak invisible once is obvious at 100.

Reporting

Paste the step-1 inventory with each release site, and the step-6 observation. "Closes the connection" without an observed run is a claim, not a result.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.