[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M789GVCM3QP2DP0N1TXBA3D1":3,"skill-same:01M789GVCM3QP2DP0N1TXBA3D1":210},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":15,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":19,"linkOnly":20,"markdownContent":21,"markdownBody":22,"frontMatter":23,"refName":24,"commitSha":25,"skillDirectory":5,"forksCount":26,"repositoryHtmlUrl":27,"repositoryHomepageUrl":28,"ownerHtmlUrl":29,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":30,"install":31,"resources":44},"01M789GVCM3QP2DP0N1TXBA3D1","pytest-agent",null,"How to run pytest and read its results when the pytest-agent plugin is installed. Use when running tests, investigating a test failure, deciding whether a failure is new, re-running failures, profiling a slow test, or diagnosing a hung or killed run.","nixidae\u002Fnanopynix","nixidae","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F314243010?v=4","01M789GD9EED7CKZH51N43BRAE","01M789GDA217X3W7930P0HFB0G",1,"Apache-2.0","https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002FSKILL.md",[],[],"2026-11-15 14:19:17",false,"---\nname: pytest-agent\ndescription: How to run pytest and read its results when the pytest-agent plugin is installed. Use when running tests, investigating a test failure, deciding whether a failure is new, re-running failures, profiling a slow test, or diagnosing a hung or killed run.\n---\n\n# pytest-agent\n\npytest-agent turns a pytest run into two separate things: a terminal that stays\nquiet, and a full record on disk that you query afterwards. If it is installed,\nit is already on — it auto-activates when a coding-agent harness env var\n(`CLAUDECODE`, `CURSOR_AGENT`, `GEMINI_CLI`, `CODEX_SANDBOX`, `AI_AGENT`, ...)\nis present, with no flag needed.\n\nThe consequence for you: **the terminal is not where the answer is.** A run\nprints a progress line every 10s and one path per failure. Everything else —\ntracebacks, captured stdout\u002Fstderr\u002Flogging, per-test records — is on disk, and\nthere are commands that read it.\n\n## The one hard rule\n\n**Never pipe pytest's stdout into `head`, `tail`, `cut`, `sed`, `awk`, or any\ngrep.** The plugin detects this and refuses to run: exit code 2, before a\nsingle test is collected. This guard is independent of agent mode and is *not*\nturned off by `PYTEST_AGENT_NO_AUTODETECT=1`.\n\nThere is nothing to work around here. Those pipes exist to cut output down,\nand the output is already cut down — piping it only discards the one line\nnaming the failure you were looking for. Run it unpiped and query afterwards.\n\nReaching for a different grep does not help: `ugrep`, `rg`, `ag`, `ack` and\n`pcregrep` are all recognized, and the reader is identified by argv[0], `comm`\n*and* `\u002Fproc\u002F\u003Cpid>\u002Fexe`, so a wrapper or alias that renames one of them is\nstill caught. If you see `refusing to run -- stdout is piped directly into\n'ugrep (running as .claude-wrapped)'`, that is your harness's `grep` shim, and\nthe fix is the same: drop the pipe.\n\nPiping into `tee` is fine — it preserves everything. So is `| wc -l`.\nListing-only runs are exempt (`--collect-only`, `--fixtures`,\n`--fixtures-per-test`, `--markers`, `--setup-plan`, `--help`, `--version`), so\n`pytest --collect-only -q | tail -1` works. `--setup-only` is not exempt — it\nreally executes fixtures.\n\nThe query subcommands below are ordinary read-only programs. Grepping *those*\nis fine.\n\n## The loop\n\n```sh\npytest tests\u002F                    # unpiped; detail goes to .pytest-agent\u002Fruns-NNNN\u002F\npytest-agent digest              # what broke, grouped by root cause\npytest-agent show '\u003Cnodeid>'     # one failure, in full\n# ...fix...\npytest-agent rerun               # just the failures, not the suite\npytest-agent compare             # what the fix actually changed\n```\n\nStart with `digest`, not `last-failures`: 18 failures with one cause read as\none entry, which tells you immediately whether you are looking at one bug or\neighteen.\n\n## Commands\n\n| Command | Answers |\n| --- | --- |\n| `pytest-agent digest` | Failures grouped by root cause, each with a count and the first-party traceback frames |\n| `pytest-agent last-failures` | Every failing test, each with the path to its log |\n| `pytest-agent last-failures --detail` | ...with every failure's full log inlined |\n| `pytest-agent show '\u003Cpattern>'` | One test's full detail. `\u003Cpattern>` is a nodeid or any substring matching exactly one; `-a` prints all matches instead of refusing an ambiguous one |\n| `pytest-agent history '\u003Cpattern>'` | That test's outcome in every run still on disk — did I break this, or was it already failing? `--limit N` for the newest N runs |\n| `pytest-agent compare [OLD NEW]` | Newly failing \u002F newly passing \u002F still failing between two runs (default: the two newest) |\n| `pytest-agent rerun [pytest args...]` | Re-run exactly the recorded failures. The only subcommand that starts a pytest session |\n| `pytest-agent watch` | Follows a run that is **still going** and prints one line per failure, stuck test, finish or death. See \"Long runs in the background\" |\n| `pytest-agent help` | The above, with flags |\n\n`show`, `last-failures`, `digest`, `watch` and `rerun` take `--run N|LABEL` to read a\nrun other than the newest. `history` and `compare` read across runs instead —\n`history` has no `--run`, and `compare` names its two runs positionally (either\norder). All take `--dir PATH`; by default they find the nearest\n`.pytest-agent` at or above the cwd.\n\nAnything that is not one of those words is forwarded to pytest with `--agent`:\n`pytest-agent -x tests\u002F` is `pytest --agent -x tests\u002F`. `python -m pytest_agent\n...` is the same CLI when the console script is not on `PATH`.\n\n### `rerun` vs pytest's `--lf`\n\n`--lf` needs no plugin and handles the common case. Reach for `rerun` when it\ncannot: pytest's cache holds only the last run per rootdir, so the first\n`-k`-filtered or `-x` re-run overwrites the list you were working through —\nwhereas every run on disk keeps its own, addressable by `--run N|LABEL`. Ids\nalso go straight into pytest's argv, so nothing has to quote\n`test_hover[in_process-local]`.\n\n## Where things are\n\nEach invocation gets its own `runs-NNNN\u002F`, so nothing is ever overwritten. The\nrun directory is printed in the startup and final banner lines. To find the\nnewest without having seen a banner:\n\n```sh\ntail -n1 .pytest-agent\u002Fhistory.jsonl | jq -r .run_dir\n```\n\nThere is deliberately no \"latest\" symlink — under concurrent runs, anything\nthat swaps the same path is a race.\n\n```\n.pytest-agent\u002F\n  history.jsonl               # one line per run, ever; never pruned\n  runs-0002\u002F\n    index.jsonl               # one record per test: outcome, duration, notes,\n                              # and for failures `crash` + `frames`\n    summary.json              # this run's counts\n    meta.json                 # written *before* the first test: run number,\n                              # label, pid, argv -- so an in-progress run is\n                              # already queryable\n    status.json               # rewritten while the run goes: the test running\n                              # now and its age. `pytest-agent watch` reads it\n                              # (the three JSON files divide by lifetime:\n                              # what the run is, where it is, what it did)\n    terminal.txt              # what plain pytest would have printed, verbatim\n    reports.txt               # only the other-plugin reports from it (--cov, ...)\n    notes.jsonl               # one line per note() call\n    collect_errors\u002F           # one log per module that failed to import\n    tests\u002Ftest_foo.py\u002F\n      test_bar.log            # the file to read: traceback + captured output\n      test_bar.json           # the same record as in index.jsonl\n      test_bar.files\u002F         # whatever the test attached\n      test_bar.stuck.txt      # thread stacks, if it ran long enough\n```\n\nThe failure lines printed at the end are paths, with `::` written as a\ndirectory separator, resolved and shell-quoted so they can be read back\ndirectly. A nodeid is appended in parentheses only when the path lost\nsomething (a `\u002F` in a parametrized id becomes `_` on disk) — when it is there,\nit is the addressable form, and it feeds straight back into `pytest-agent\nshow`.\n\nReading the files directly is a first-class option:\n\n```sh\njq -c 'select(.outcome == \"failed\" or .outcome == \"error\")' .pytest-agent\u002Fruns-0002\u002Findex.jsonl\njq -c 'select(.nodeid == \"tests\u002Ftest_foo.py::test_bar\") | .duration_s' .pytest-agent\u002Fruns-*\u002Findex.jsonl\n```\n\n## Long runs in the background\n\nName a slow suite so you can find it again without knowing how many runs\nhappened while it went:\n\n```sh\npytest --agent-label full-suite tests\u002F     # 10 minutes, in the background\npytest tests\u002Ftest_parser.py                # meanwhile, focused runs\npytest-agent last-failures --run full-suite\npytest-agent rerun --run full-suite\n```\n\n- The label works **while the run is still going** — querying an unfinished\n  run answers from what it has recorded so far and says so on stderr.\n- Labeled runs get their own retention budget, so focused runs cannot evict\n  the labeled one.\n- A label is never all-digits (that would be a run number); letters, digits,\n  `.`, `_`, `-`, up to 64 characters. Labels need not be unique — the newest\n  match wins.\n- `PYTEST_AGENT_LABEL` does the same for a whole shell or CI job.\n\n### Do not poll the run — watch it\n\n`pytest-agent watch` follows a run that is still going and prints **one line\nper event**. It ends when the run ends.\n\n```sh\npytest --agent-label bg1 tests\u002F &     # start it\npytest-agent watch --run bg1          # then follow it\n```\n\n```\nFAIL  tests\u002Ftest_parser.py::test_roundtrip -- AssertionError: 3 != 4\nSTUCK 124s tests\u002Ftest_net.py::test_timeout\nDONE  runs-0042 [bg1]: 4 failed, 835 passed, 10 skipped in 214s (exit 1) -- pytest-agent digest --run bg1\n```\n\n**In Claude Code, pair a background `Bash` with one `Monitor`.** Each line of\nstdout becomes a notification, and the command exits by itself, so the monitor\ndoes not stay armed after the run is over:\n\n```\nBash(run_in_background=true):  direnv exec . pytest tests --agent-label bg1\nMonitor(command=\"direnv exec . pytest-agent watch --run bg1 --wait 120\",\n        description=\"pytest bg1: failures, stuck tests, finish\",\n        timeout_ms=3600000)\n```\n\nThen wait. Do not poll the files yourself, and do not build a `tail -f` or a\n`grep` loop — those are what this replaces.\n\n- **Set `timeout_ms` above the length of the suite.** The default is five\n  minutes.\n- **`--wait` (60s) means the watcher may be armed first**, which is the usual\n  order. It waits for the `.pytest-agent` directory too.\n- **`DIED` is why silence is meaningful.** A segfault or an OOM kill is an\n  event, not an absence, so nothing happening really does mean the run is\n  healthy. Exit status: `0` clean, `2` failures, `3` died, `1` could not\n  watch.\n- **`--stuck-after` (120s) reports a slow test; it is not the run's own\n  `--agent-stuck-after` (300s), which dumps stacks.** When a dump exists, the\n  `STUCK` line names it.\n- The failure list is capped at ten lines. The totals come with `DONE`, and\n  `pytest-agent digest --run bg1` gives the rest.\n- **Reusing a label is safe.** `watch` prefers the run of that name which is\n  still going, so it cannot attach to yesterday's run of the same name.\n\n## Getting values out of a test\n\nA `print()` already lands in that test's log. Notes do what it cannot: be\nqueried across a run, hold something too big for a line, or come from deep\ninside the code under test.\n\n```python\ndef test_resolution(agent_notes):\n    agent_notes.note(store=store_path, backend=backend)  # structured, queryable\n    agent_notes.attach(\"payload.json\", raw_response)  # too big for a line\n    (agent_notes.dir \u002F \"dump.bin\").write_bytes(blob)  # or write files yourself\n```\n\n```python\nfrom pytest_agent import note  # no fixture; callable from anywhere at all\n\n\ndef resolve(digest):  # ...including inside the code under test\n    note(resolving=digest)\n```\n\nA note lands in the end-of-run summary (so you read it in the same turn that\nran the test), in `notes.jsonl`, in `index.jsonl` as `notes`, and in the test's\n`.log` above the traceback. Values need not be JSON-serializable (anything else\nis recorded as its `repr` — a probe must never fail the test it was added to);\na repeated key collapses to its last value everywhere except `notes.jsonl`; and\nvalues over 2000 characters are clipped everywhere except `notes.jsonl`.\n\n### Prefer a throwaway test to `python -c`\n\nA one-off `python -c '...'` throws away the fixtures, the configured\nenvironment, and the imports that already work. A scratch test file costs the\nsame to write and keeps all of it:\n\n```python\ndef test_scratch_what_does_resolve_return(agent_notes, store):\n    agent_notes.note(result=resolve(store, \"0lqkw72fqnp7q1kr\"))\n```\n\n`pytest tests\u002Ftest_scratch.py` prints the answer in its summary. Delete the\nfile when the question is answered.\n\n### Profiling\n\nAdd `profile` as a test parameter — no other change to the body:\n\n```python\ndef test_something_slow(profile):\n    do_the_slow_thing()\n```\n\nA pyinstrument text report lands at `test_something_slow.profile.txt` beside\nthat test's other output.\n\n## When a run hangs or is killed\n\nBoth cases leave evidence; look for it before re-running.\n\n- **A test that keeps running** gets every thread's stack appended to\n  `\u003Ctest>.stuck.txt` after `--agent-stuck-after` seconds (300 by default,\n  chosen to fire *before* the `timeout 500 pytest` an agent typically uses),\n  and the path is printed. It repeats up to five times per test — identical\n  stacks mean wedged, moving stacks mean slow.\n- **A killed run still reports.** SIGTERM becomes the interrupt pytest handles\n  gracefully, so the run still prints its summary, still appends to\n  `history.jsonl`, and names what it died in. `summary.json` and\n  `history.jsonl` carry `interrupted_at` and `killed_by`.\n\nSo after a `timeout ... pytest` that was killed: read the summary block, then\nthe `.stuck.txt` it names. A second SIGTERM always kills outright, so an\nunresponsive run stays killable.\n\nIf pytest's own `faulthandler_timeout` ini option is set, it keeps working —\npytest-agent leaves that timer alone. It fires even when the main thread never\nreleases the GIL, which the Python watchdog cannot cover; use it (with\n`--agent-stuck-after=0`) if all you want is \"dump and die\".\n\n## Options\n\n| Flag | Env var | Default | Meaning |\n| --- | --- | --- | --- |\n| `--agent` | `PYTEST_AGENT` | auto-detected | Turn on agent mode |\n| `--agent-dir` | `PYTEST_AGENT_DIR` | `.pytest-agent` | Where run detail goes (relative to rootdir) |\n| `--agent-label` | `PYTEST_AGENT_LABEL` | *(none)* | Name this run for later queries |\n| `--agent-heartbeat` | `PYTEST_AGENT_HEARTBEAT` | `10` | Seconds between progress lines (0 prints none) |\n| `--agent-stuck-after` | `PYTEST_AGENT_STUCK_AFTER` | `300` | Dump thread stacks after one test runs this long (0 disables) |\n| `--agent-status-interval` | `PYTEST_AGENT_STATUS_INTERVAL` | `2` | Seconds between rewrites of `status.json`, which `pytest-agent watch` reads (0 writes none) |\n| `--agent-keep-runs` | `PYTEST_AGENT_KEEP_RUNS` | `20` | Keep the newest N `runs-*` dirs; labeled runs get a second budget of the same size; `history.jsonl` is kept forever |\n| n\u002Fa | `PYTEST_AGENT_NO_AUTODETECT` | off | Disable harness auto-activation |\n\n(The guard has an override. It is documented in the README, for a human who\nmeans it, and left out here on purpose: an agent that hits the refusal should\nstop truncating, not learn a flag for truncating anyway.)\n\n## Things that will otherwise surprise you\n\n- **Other plugins' end-of-run reports are not lost.** Agent mode redirects\n  pytest's builtin reporter to `terminal.txt`, then reprints what any plugin\n  wrote at the end — coverage tables, `--durations`, `--junit-xml`'s path.\n  Short ones print inline. One longer than `--agent-max-summary-lines` (40) is\n  **not printed in part** — you get its first line and a pointer, because half\n  a coverage table reads like a whole one. Read the run's `reports.txt`, which\n  holds *only* those reports (unlike `terminal.txt`, the whole pytest\n  transcript), or re-run with `--agent-max-summary-lines=0` to have it inline.\n  With `--cov` the overall percentage also gets a `[pytest-agent] coverage: NN%`\n  line of its own, so the headline number never needs the file at all.\n- **`terminal.txt` is the file to read** when the problem is with pytest's own\n  reporting rather than with a test.\n- **`--agent-keep-runs` prunes old run directories**, so `history` and\n  `compare` say how many runs they actually read. \"Failed in 2 of 3 runs\" means\n  three runs *still on disk*, not three runs ever.\n- **pytest-xdist (`-n N`) works with agent mode**, and is the way to make a\n  slow suite bearable. Only the controller records -- it receives every\n  worker's report, tracebacks and captured output included -- so you still get\n  one run directory and one `history.jsonl` entry. Two things degrade, and the\n  banner says so at startup: `note()`\u002F`attach()` land inline in the test's\n  `.log` instead of `notes.jsonl`\u002Fthe index, and stuck-test dumps are off\n  (a dump from the controller would name an innocent test). Drop `-n` when you\n  need either of those.\n- **A run that cannot write its directory turns agent mode off** rather than\n  failing the run, and says so loudly on stderr. If you see that, the next\n  query will answer from a *stale* run — fix `--agent-dir` before believing it.\n","# pytest-agent\n\npytest-agent turns a pytest run into two separate things: a terminal that stays\nquiet, and a full record on disk that you query afterwards. If it is installed,\nit is already on — it auto-activates when a coding-agent harness env var\n(`CLAUDECODE`, `CURSOR_AGENT`, `GEMINI_CLI`, `CODEX_SANDBOX`, `AI_AGENT`, ...)\nis present, with no flag needed.\n\nThe consequence for you: **the terminal is not where the answer is.** A run\nprints a progress line every 10s and one path per failure. Everything else —\ntracebacks, captured stdout\u002Fstderr\u002Flogging, per-test records — is on disk, and\nthere are commands that read it.\n\n## The one hard rule\n\n**Never pipe pytest's stdout into `head`, `tail`, `cut`, `sed`, `awk`, or any\ngrep.** The plugin detects this and refuses to run: exit code 2, before a\nsingle test is collected. This guard is independent of agent mode and is *not*\nturned off by `PYTEST_AGENT_NO_AUTODETECT=1`.\n\nThere is nothing to work around here. Those pipes exist to cut output down,\nand the output is already cut down — piping it only discards the one line\nnaming the failure you were looking for. Run it unpiped and query afterwards.\n\nReaching for a different grep does not help: `ugrep`, `rg`, `ag`, `ack` and\n`pcregrep` are all recognized, and the reader is identified by argv[0], `comm`\n*and* `\u002Fproc\u002F\u003Cpid>\u002Fexe`, so a wrapper or alias that renames one of them is\nstill caught. If you see `refusing to run -- stdout is piped directly into\n'ugrep (running as .claude-wrapped)'`, that is your harness's `grep` shim, and\nthe fix is the same: drop the pipe.\n\nPiping into `tee` is fine — it preserves everything. So is `| wc -l`.\nListing-only runs are exempt (`--collect-only`, `--fixtures`,\n`--fixtures-per-test`, `--markers`, `--setup-plan`, `--help`, `--version`), so\n`pytest --collect-only -q | tail -1` works. `--setup-only` is not exempt — it\nreally executes fixtures.\n\nThe query subcommands below are ordinary read-only programs. Grepping *those*\nis fine.\n\n## The loop\n\n```sh\npytest tests\u002F                    # unpiped; detail goes to .pytest-agent\u002Fruns-NNNN\u002F\npytest-agent digest              # what broke, grouped by root cause\npytest-agent show '\u003Cnodeid>'     # one failure, in full\n# ...fix...\npytest-agent rerun               # just the failures, not the suite\npytest-agent compare             # what the fix actually changed\n```\n\nStart with `digest`, not `last-failures`: 18 failures with one cause read as\none entry, which tells you immediately whether you are looking at one bug or\neighteen.\n\n## Commands\n\n| Command | Answers |\n| --- | --- |\n| `pytest-agent digest` | Failures grouped by root cause, each with a count and the first-party traceback frames |\n| `pytest-agent last-failures` | Every failing test, each with the path to its log |\n| `pytest-agent last-failures --detail` | ...with every failure's full log inlined |\n| `pytest-agent show '\u003Cpattern>'` | One test's full detail. `\u003Cpattern>` is a nodeid or any substring matching exactly one; `-a` prints all matches instead of refusing an ambiguous one |\n| `pytest-agent history '\u003Cpattern>'` | That test's outcome in every run still on disk — did I break this, or was it already failing? `--limit N` for the newest N runs |\n| `pytest-agent compare [OLD NEW]` | Newly failing \u002F newly passing \u002F still failing between two runs (default: the two newest) |\n| `pytest-agent rerun [pytest args...]` | Re-run exactly the recorded failures. The only subcommand that starts a pytest session |\n| `pytest-agent watch` | Follows a run that is **still going** and prints one line per failure, stuck test, finish or death. See \"Long runs in the background\" |\n| `pytest-agent help` | The above, with flags |\n\n`show`, `last-failures`, `digest`, `watch` and `rerun` take `--run N|LABEL` to read a\nrun other than the newest. `history` and `compare` read across runs instead —\n`history` has no `--run`, and `compare` names its two runs positionally (either\norder). All take `--dir PATH`; by default they find the nearest\n`.pytest-agent` at or above the cwd.\n\nAnything that is not one of those words is forwarded to pytest with `--agent`:\n`pytest-agent -x tests\u002F` is `pytest --agent -x tests\u002F`. `python -m pytest_agent\n...` is the same CLI when the console script is not on `PATH`.\n\n### `rerun` vs pytest's `--lf`\n\n`--lf` needs no plugin and handles the common case. Reach for `rerun` when it\ncannot: pytest's cache holds only the last run per rootdir, so the first\n`-k`-filtered or `-x` re-run overwrites the list you were working through —\nwhereas every run on disk keeps its own, addressable by `--run N|LABEL`. Ids\nalso go straight into pytest's argv, so nothing has to quote\n`test_hover[in_process-local]`.\n\n## Where things are\n\nEach invocation gets its own `runs-NNNN\u002F`, so nothing is ever overwritten. The\nrun directory is printed in the startup and final banner lines. To find the\nnewest without having seen a banner:\n\n```sh\ntail -n1 .pytest-agent\u002Fhistory.jsonl | jq -r .run_dir\n```\n\nThere is deliberately no \"latest\" symlink — under concurrent runs, anything\nthat swaps the same path is a race.\n\n```\n.pytest-agent\u002F\n  history.jsonl               # one line per run, ever; never pruned\n  runs-0002\u002F\n    index.jsonl               # one record per test: outcome, duration, notes,\n                              # and for failures `crash` + `frames`\n    summary.json              # this run's counts\n    meta.json                 # written *before* the first test: run number,\n                              # label, pid, argv -- so an in-progress run is\n                              # already queryable\n    status.json               # rewritten while the run goes: the test running\n                              # now and its age. `pytest-agent watch` reads it\n                              # (the three JSON files divide by lifetime:\n                              # what the run is, where it is, what it did)\n    terminal.txt              # what plain pytest would have printed, verbatim\n    reports.txt               # only the other-plugin reports from it (--cov, ...)\n    notes.jsonl               # one line per note() call\n    collect_errors\u002F           # one log per module that failed to import\n    tests\u002Ftest_foo.py\u002F\n      test_bar.log            # the file to read: traceback + captured output\n      test_bar.json           # the same record as in index.jsonl\n      test_bar.files\u002F         # whatever the test attached\n      test_bar.stuck.txt      # thread stacks, if it ran long enough\n```\n\nThe failure lines printed at the end are paths, with `::` written as a\ndirectory separator, resolved and shell-quoted so they can be read back\ndirectly. A nodeid is appended in parentheses only when the path lost\nsomething (a `\u002F` in a parametrized id becomes `_` on disk) — when it is there,\nit is the addressable form, and it feeds straight back into `pytest-agent\nshow`.\n\nReading the files directly is a first-class option:\n\n```sh\njq -c 'select(.outcome == \"failed\" or .outcome == \"error\")' .pytest-agent\u002Fruns-0002\u002Findex.jsonl\njq -c 'select(.nodeid == \"tests\u002Ftest_foo.py::test_bar\") | .duration_s' .pytest-agent\u002Fruns-*\u002Findex.jsonl\n```\n\n## Long runs in the background\n\nName a slow suite so you can find it again without knowing how many runs\nhappened while it went:\n\n```sh\npytest --agent-label full-suite tests\u002F     # 10 minutes, in the background\npytest tests\u002Ftest_parser.py                # meanwhile, focused runs\npytest-agent last-failures --run full-suite\npytest-agent rerun --run full-suite\n```\n\n- The label works **while the run is still going** — querying an unfinished\n  run answers from what it has recorded so far and says so on stderr.\n- Labeled runs get their own retention budget, so focused runs cannot evict\n  the labeled one.\n- A label is never all-digits (that would be a run number); letters, digits,\n  `.`, `_`, `-`, up to 64 characters. Labels need not be unique — the newest\n  match wins.\n- `PYTEST_AGENT_LABEL` does the same for a whole shell or CI job.\n\n### Do not poll the run — watch it\n\n`pytest-agent watch` follows a run that is still going and prints **one line\nper event**. It ends when the run ends.\n\n```sh\npytest --agent-label bg1 tests\u002F &     # start it\npytest-agent watch --run bg1          # then follow it\n```\n\n```\nFAIL  tests\u002Ftest_parser.py::test_roundtrip -- AssertionError: 3 != 4\nSTUCK 124s tests\u002Ftest_net.py::test_timeout\nDONE  runs-0042 [bg1]: 4 failed, 835 passed, 10 skipped in 214s (exit 1) -- pytest-agent digest --run bg1\n```\n\n**In Claude Code, pair a background `Bash` with one `Monitor`.** Each line of\nstdout becomes a notification, and the command exits by itself, so the monitor\ndoes not stay armed after the run is over:\n\n```\nBash(run_in_background=true):  direnv exec . pytest tests --agent-label bg1\nMonitor(command=\"direnv exec . pytest-agent watch --run bg1 --wait 120\",\n        description=\"pytest bg1: failures, stuck tests, finish\",\n        timeout_ms=3600000)\n```\n\nThen wait. Do not poll the files yourself, and do not build a `tail -f` or a\n`grep` loop — those are what this replaces.\n\n- **Set `timeout_ms` above the length of the suite.** The default is five\n  minutes.\n- **`--wait` (60s) means the watcher may be armed first**, which is the usual\n  order. It waits for the `.pytest-agent` directory too.\n- **`DIED` is why silence is meaningful.** A segfault or an OOM kill is an\n  event, not an absence, so nothing happening really does mean the run is\n  healthy. Exit status: `0` clean, `2` failures, `3` died, `1` could not\n  watch.\n- **`--stuck-after` (120s) reports a slow test; it is not the run's own\n  `--agent-stuck-after` (300s), which dumps stacks.** When a dump exists, the\n  `STUCK` line names it.\n- The failure list is capped at ten lines. The totals come with `DONE`, and\n  `pytest-agent digest --run bg1` gives the rest.\n- **Reusing a label is safe.** `watch` prefers the run of that name which is\n  still going, so it cannot attach to yesterday's run of the same name.\n\n## Getting values out of a test\n\nA `print()` already lands in that test's log. Notes do what it cannot: be\nqueried across a run, hold something too big for a line, or come from deep\ninside the code under test.\n\n```python\ndef test_resolution(agent_notes):\n    agent_notes.note(store=store_path, backend=backend)  # structured, queryable\n    agent_notes.attach(\"payload.json\", raw_response)  # too big for a line\n    (agent_notes.dir \u002F \"dump.bin\").write_bytes(blob)  # or write files yourself\n```\n\n```python\nfrom pytest_agent import note  # no fixture; callable from anywhere at all\n\n\ndef resolve(digest):  # ...including inside the code under test\n    note(resolving=digest)\n```\n\nA note lands in the end-of-run summary (so you read it in the same turn that\nran the test), in `notes.jsonl`, in `index.jsonl` as `notes`, and in the test's\n`.log` above the traceback. Values need not be JSON-serializable (anything else\nis recorded as its `repr` — a probe must never fail the test it was added to);\na repeated key collapses to its last value everywhere except `notes.jsonl`; and\nvalues over 2000 characters are clipped everywhere except `notes.jsonl`.\n\n### Prefer a throwaway test to `python -c`\n\nA one-off `python -c '...'` throws away the fixtures, the configured\nenvironment, and the imports that already work. A scratch test file costs the\nsame to write and keeps all of it:\n\n```python\ndef test_scratch_what_does_resolve_return(agent_notes, store):\n    agent_notes.note(result=resolve(store, \"0lqkw72fqnp7q1kr\"))\n```\n\n`pytest tests\u002Ftest_scratch.py` prints the answer in its summary. Delete the\nfile when the question is answered.\n\n### Profiling\n\nAdd `profile` as a test parameter — no other change to the body:\n\n```python\ndef test_something_slow(profile):\n    do_the_slow_thing()\n```\n\nA pyinstrument text report lands at `test_something_slow.profile.txt` beside\nthat test's other output.\n\n## When a run hangs or is killed\n\nBoth cases leave evidence; look for it before re-running.\n\n- **A test that keeps running** gets every thread's stack appended to\n  `\u003Ctest>.stuck.txt` after `--agent-stuck-after` seconds (300 by default,\n  chosen to fire *before* the `timeout 500 pytest` an agent typically uses),\n  and the path is printed. It repeats up to five times per test — identical\n  stacks mean wedged, moving stacks mean slow.\n- **A killed run still reports.** SIGTERM becomes the interrupt pytest handles\n  gracefully, so the run still prints its summary, still appends to\n  `history.jsonl`, and names what it died in. `summary.json` and\n  `history.jsonl` carry `interrupted_at` and `killed_by`.\n\nSo after a `timeout ... pytest` that was killed: read the summary block, then\nthe `.stuck.txt` it names. A second SIGTERM always kills outright, so an\nunresponsive run stays killable.\n\nIf pytest's own `faulthandler_timeout` ini option is set, it keeps working —\npytest-agent leaves that timer alone. It fires even when the main thread never\nreleases the GIL, which the Python watchdog cannot cover; use it (with\n`--agent-stuck-after=0`) if all you want is \"dump and die\".\n\n## Options\n\n| Flag | Env var | Default | Meaning |\n| --- | --- | --- | --- |\n| `--agent` | `PYTEST_AGENT` | auto-detected | Turn on agent mode |\n| `--agent-dir` | `PYTEST_AGENT_DIR` | `.pytest-agent` | Where run detail goes (relative to rootdir) |\n| `--agent-label` | `PYTEST_AGENT_LABEL` | *(none)* | Name this run for later queries |\n| `--agent-heartbeat` | `PYTEST_AGENT_HEARTBEAT` | `10` | Seconds between progress lines (0 prints none) |\n| `--agent-stuck-after` | `PYTEST_AGENT_STUCK_AFTER` | `300` | Dump thread stacks after one test runs this long (0 disables) |\n| `--agent-status-interval` | `PYTEST_AGENT_STATUS_INTERVAL` | `2` | Seconds between rewrites of `status.json`, which `pytest-agent watch` reads (0 writes none) |\n| `--agent-keep-runs` | `PYTEST_AGENT_KEEP_RUNS` | `20` | Keep the newest N `runs-*` dirs; labeled runs get a second budget of the same size; `history.jsonl` is kept forever |\n| n\u002Fa | `PYTEST_AGENT_NO_AUTODETECT` | off | Disable harness auto-activation |\n\n(The guard has an override. It is documented in the README, for a human who\nmeans it, and left out here on purpose: an agent that hits the refusal should\nstop truncating, not learn a flag for truncating anyway.)\n\n## Things that will otherwise surprise you\n\n- **Other plugins' end-of-run reports are not lost.** Agent mode redirects\n  pytest's builtin reporter to `terminal.txt`, then reprints what any plugin\n  wrote at the end — coverage tables, `--durations`, `--junit-xml`'s path.\n  Short ones print inline. One longer than `--agent-max-summary-lines` (40) is\n  **not printed in part** — you get its first line and a pointer, because half\n  a coverage table reads like a whole one. Read the run's `reports.txt`, which\n  holds *only* those reports (unlike `terminal.txt`, the whole pytest\n  transcript), or re-run with `--agent-max-summary-lines=0` to have it inline.\n  With `--cov` the overall percentage also gets a `[pytest-agent] coverage: NN%`\n  line of its own, so the headline number never needs the file at all.\n- **`terminal.txt` is the file to read** when the problem is with pytest's own\n  reporting rather than with a test.\n- **`--agent-keep-runs` prunes old run directories**, so `history` and\n  `compare` say how many runs they actually read. \"Failed in 2 of 3 runs\" means\n  three runs *still on disk*, not three runs ever.\n- **pytest-xdist (`-n N`) works with agent mode**, and is the way to make a\n  slow suite bearable. Only the controller records -- it receives every\n  worker's report, tracebacks and captured output included -- so you still get\n  one run directory and one `history.jsonl` entry. Two things degrade, and the\n  banner says so at startup: `note()`\u002F`attach()` land inline in the test's\n  `.log` instead of `notes.jsonl`\u002Fthe index, and stuck-test dumps are off\n  (a dump from the controller would name an innocent test). Drop `-n` when you\n  need either of those.\n- **A run that cannot write its directory turns agent mode off** rather than\n  failing the run, and says so loudly on stderr. If you see that, the next\n  query will answer from a *stale* run — fix `--agent-dir` before believing it.\n",{"name":5,"description":7},"main","751307b536b29461f62726b09cff2a0fe1f8e833",0,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix","https:\u002F\u002Fnixidae.github.io\u002Fnanopynix\u002F","https:\u002F\u002Fgithub.com\u002Fnixidae","2026-11-15 05:18:27",{"source":8,"skillName":5,"commands":32,"prompt":42,"zipAvailable":43},[33,36,39],{"tool":34,"command":35},"npx","npx skills add nixidae\u002Fnanopynix --skill pytest-agent",{"tool":37,"command":38},"bunx","bunx skills add nixidae\u002Fnanopynix --skill pytest-agent",{"tool":40,"command":41},"pnpm","pnpm dlx skills add nixidae\u002Fnanopynix --skill pytest-agent","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002FSKILL.md",true,[45,49,55,59,65,71,75,79,83,87,91,95,99,103,107,111,115,119,123,127,131,135,139,142,146,150,154,158,162,166,170,174,178,182,186,190,194,198,202,206],{"path":46,"resourceType":46,"resourceTypeDesc":47,"sizeBytes":48,"sourceHtmlUrl":15},"LICENSE","许可证",11358,{"path":50,"resourceType":51,"resourceTypeDesc":52,"sizeBytes":53,"sourceHtmlUrl":54},"mypy.ini","OTHER","其他",98,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Fmypy.ini",{"path":56,"resourceType":51,"resourceTypeDesc":52,"sizeBytes":57,"sourceHtmlUrl":58},"pyproject.toml",880,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Fpyproject.toml",{"path":60,"resourceType":61,"resourceTypeDesc":62,"sizeBytes":63,"sourceHtmlUrl":64},"README.md","REFERENCE","参考文档",35623,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002FREADME.md",{"path":66,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":69,"sourceHtmlUrl":70},"src\u002Fpytest_agent\u002F__init__.py","SCRIPT","脚本",192,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Fsrc\u002Fpytest_agent\u002F__init__.py",{"path":72,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":73,"sourceHtmlUrl":74},"src\u002Fpytest_agent\u002F__main__.py",451,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Fsrc\u002Fpytest_agent\u002F__main__.py",{"path":76,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":77,"sourceHtmlUrl":78},"src\u002Fpytest_agent\u002F_capture.py",22667,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Fsrc\u002Fpytest_agent\u002F_capture.py",{"path":80,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":81,"sourceHtmlUrl":82},"src\u002Fpytest_agent\u002F_crash.py",6845,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Fsrc\u002Fpytest_agent\u002F_crash.py",{"path":84,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":85,"sourceHtmlUrl":86},"src\u002Fpytest_agent\u002F_entry_points.py",723,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Fsrc\u002Fpytest_agent\u002F_entry_points.py",{"path":88,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":89,"sourceHtmlUrl":90},"src\u002Fpytest_agent\u002F_harness_detect.py",1776,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Fsrc\u002Fpytest_agent\u002F_harness_detect.py",{"path":92,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":93,"sourceHtmlUrl":94},"src\u002Fpytest_agent\u002F_history.py",10734,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Fsrc\u002Fpytest_agent\u002F_history.py",{"path":96,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":97,"sourceHtmlUrl":98},"src\u002Fpytest_agent\u002F_notes.py",8280,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Fsrc\u002Fpytest_agent\u002F_notes.py",{"path":100,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":101,"sourceHtmlUrl":102},"src\u002Fpytest_agent\u002F_paths.py",1408,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Fsrc\u002Fpytest_agent\u002F_paths.py",{"path":104,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":105,"sourceHtmlUrl":106},"src\u002Fpytest_agent\u002F_pipe_guard.py",7837,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Fsrc\u002Fpytest_agent\u002F_pipe_guard.py",{"path":108,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":109,"sourceHtmlUrl":110},"src\u002Fpytest_agent\u002F_profile.py",2616,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Fsrc\u002Fpytest_agent\u002F_profile.py",{"path":112,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":113,"sourceHtmlUrl":114},"src\u002Fpytest_agent\u002F_query.py",31132,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Fsrc\u002Fpytest_agent\u002F_query.py",{"path":116,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":117,"sourceHtmlUrl":118},"src\u002Fpytest_agent\u002F_records.py",6837,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Fsrc\u002Fpytest_agent\u002F_records.py",{"path":120,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":121,"sourceHtmlUrl":122},"src\u002Fpytest_agent\u002F_runtime.py",40603,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Fsrc\u002Fpytest_agent\u002F_runtime.py",{"path":124,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":125,"sourceHtmlUrl":126},"src\u002Fpytest_agent\u002F_terminal.py",1076,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Fsrc\u002Fpytest_agent\u002F_terminal.py",{"path":128,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":129,"sourceHtmlUrl":130},"src\u002Fpytest_agent\u002F_watch.py",25822,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Fsrc\u002Fpytest_agent\u002F_watch.py",{"path":132,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":133,"sourceHtmlUrl":134},"src\u002Fpytest_agent\u002Fcli.py",5885,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Fsrc\u002Fpytest_agent\u002Fcli.py",{"path":136,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":137,"sourceHtmlUrl":138},"src\u002Fpytest_agent\u002Fplugin.py",17146,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Fsrc\u002Fpytest_agent\u002Fplugin.py",{"path":140,"resourceType":51,"resourceTypeDesc":52,"sizeBytes":26,"sourceHtmlUrl":141},"src\u002Fpytest_agent\u002Fpy.typed","https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Fsrc\u002Fpytest_agent\u002Fpy.typed",{"path":143,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":144,"sourceHtmlUrl":145},"tests\u002Fconftest.py",6596,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Ftests\u002Fconftest.py",{"path":147,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":148,"sourceHtmlUrl":149},"tests\u002Ftest_agent_workflow.py",13501,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Ftests\u002Ftest_agent_workflow.py",{"path":151,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":152,"sourceHtmlUrl":153},"tests\u002Ftest_autodetect_integration.py",2213,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Ftests\u002Ftest_autodetect_integration.py",{"path":155,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":156,"sourceHtmlUrl":157},"tests\u002Ftest_capture.py",8403,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Ftests\u002Ftest_capture.py",{"path":159,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":160,"sourceHtmlUrl":161},"tests\u002Ftest_cli_dispatch.py",1134,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Ftests\u002Ftest_cli_dispatch.py",{"path":163,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":164,"sourceHtmlUrl":165},"tests\u002Ftest_crash.py",5323,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Ftests\u002Ftest_crash.py",{"path":167,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":168,"sourceHtmlUrl":169},"tests\u002Ftest_harness_detect.py",955,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Ftests\u002Ftest_harness_detect.py",{"path":171,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":172,"sourceHtmlUrl":173},"tests\u002Ftest_heartbeat_backoff.py",2442,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Ftests\u002Ftest_heartbeat_backoff.py",{"path":175,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":176,"sourceHtmlUrl":177},"tests\u002Ftest_history.py",10074,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Ftests\u002Ftest_history.py",{"path":179,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":180,"sourceHtmlUrl":181},"tests\u002Ftest_integration.py",34952,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Ftests\u002Ftest_integration.py",{"path":183,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":184,"sourceHtmlUrl":185},"tests\u002Ftest_interrupt.py",9827,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Ftests\u002Ftest_interrupt.py",{"path":187,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":188,"sourceHtmlUrl":189},"tests\u002Ftest_notes.py",14351,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Ftests\u002Ftest_notes.py",{"path":191,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":192,"sourceHtmlUrl":193},"tests\u002Ftest_pipe_guard.py",8826,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Ftests\u002Ftest_pipe_guard.py",{"path":195,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":196,"sourceHtmlUrl":197},"tests\u002Ftest_profile.py",1816,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Ftests\u002Ftest_profile.py",{"path":199,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":200,"sourceHtmlUrl":201},"tests\u002Ftest_query.py",29293,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Ftests\u002Ftest_query.py",{"path":203,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":204,"sourceHtmlUrl":205},"tests\u002Ftest_watch.py",22329,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002Ftests\u002Ftest_watch.py",{"path":207,"resourceType":61,"resourceTypeDesc":62,"sizeBytes":208,"sourceHtmlUrl":209},"TODO.md",25776,"https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpytest-agent\u002FTODO.md",[211,218],{"skillNo":212,"name":213,"nameZh":6,"description":214,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":215,"categories":216,"keywords":217,"publishedAt":19},"01M789GKWREPPVKTSS0W80X7AX","pynix","Run Nix through the pynix CLI instead of the nix CLI -- eval, build, develop, print-dev-env, path-info, log, why-depends, copy, store, search, flake, config, registry. Load before any Nix command; fall back to nix only for what pynix lacks.","https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002Fpynix\u002Fskill\u002Fpynix\u002FSKILL.md",[],[],{"skillNo":219,"name":5,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":220,"categories":221,"keywords":222,"publishedAt":19},"01M789GJKPD4WRGV35XD2KC43D","https:\u002F\u002Fgithub.com\u002Fnixidae\u002Fnanopynix\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fpytest-agent\u002FSKILL.md",[],[]]