{"record":{"id":"311efec487881850","repo":"pestphp/pest","slug":"the-afterall-hook-may-not-be-used-inside-a-desc","errorCode":null,"errorMessage":"The [afterAll] hook may not be used inside a [describe] block. Please move it to the top level of [%s].","messagePattern":"The \\[afterAll\\] hook may not be used inside a \\[describe\\] block\\. Please move it to the top level of \\[(.+?)\\]\\.","errorType":"exception","errorClass":"AfterAllWithinDescribe","httpStatus":null,"severity":"error","filePath":"src/Functions.php","lineNumber":161,"sourceCode":"if (! function_exists('afterEach')) {\n    /**\n     * @param-closure-this TestCall  $closure\n     */\n    function afterEach(?Closure $closure = null): AfterEachCall\n    {\n        $filename = Backtrace::testFile();\n\n        return new AfterEachCall(TestSuite::getInstance(), $filename, $closure);\n    }\n}\n\nif (! function_exists('afterAll')) {\n    function afterAll(Closure $closure): void\n    {\n        if (DescribeCall::describing() !== []) {\n            $filename = Backtrace::testFile();\n\n            throw new AfterAllWithinDescribe($filename);\n        }\n\n        TestSuite::getInstance()->afterAll->set($closure);\n    }\n}\n\nif (! function_exists('covers')) {\n    /**\n     * @param  array<int, string>|string  $classesOrFunctions\n     */\n    function covers(array|string ...$classesOrFunctions): void\n    {\n        $filename = Backtrace::testFile();\n\n        $beforeEachCall = (new BeforeEachCall(TestSuite::getInstance(), $filename));\n\n        $beforeEachCall->covers(...$classesOrFunctions);\n        $beforeEachCall->group('__pest_mutate_only');","sourceCodeStart":143,"sourceCodeEnd":179,"githubUrl":"https://github.com/pestphp/pest/blob/1af74a215c3a89bc7a44fc6f9322e369eaad2423/src/Functions.php#L143-L179","documentation":"Symmetric to beforeAll: afterAll() registers a once-per-file teardown hook on the TestSuite, and calling it inside a describe() block throws AfterAllWithinDescribe with the file name. Suite-level All-hooks must live at the top level of the test file; each-variants are unrestricted.","triggerScenarios":"Calling afterAll(function () { ... }) inside describe('...', function () { ... }); refactoring teardown into groups; copy-pasting a describe body that contained hooks from a Jest-style project.","commonSituations":"Grouping tests late in a file's life and moving hooks with them; migrating from Jest/Jasmine where afterAll inside describe is standard; attempting teardown scoped to one feature group.","solutions":["Move the afterAll() call to the top level of the test file.","Use afterEach() inside the describe for group-scoped teardown.","Split the describe into its own file if it truly needs file-scoped teardown independent of other groups."],"exampleFix":"// before\ndescribe('imports', function () {\n    afterAll(fn () => cleanupStorage()); // throws\n    it('imports csv', fn () => ...);\n});\n// after\nafterAll(fn () => cleanupStorage());\ndescribe('imports', function () {\n    it('imports csv', fn () => ...);\n});","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep afterAll at the file's top level, grouped with beforeAll for readability.","Use afterEach() inside describe blocks for scoped teardown.","When porting Jest/Jasmine suites, remember only Pest's each-hooks may nest inside describe."],"tags":["pest","hooks","after-all","describe","scope"],"backgroundTag":"hook-in-invalid-scope","analyzedSha":"1af74a215c3a89bc7a44fc6f9322e369eaad2423","analyzedAt":"2026-08-21T02:16:38.132Z","schemaVersion":2},"datasetVersion":"2026-08-21T03:17:12.404Z"}