{"record":{"id":"15f514ea6027ac80","repo":"flowable/flowable-engine","slug":"unable-to-peek-empty-agenda","errorCode":null,"errorMessage":"Unable to peek empty agenda.","messagePattern":"Unable to peek empty agenda\\.","errorType":"exception","errorClass":"FlowableException","httpStatus":null,"severity":"error","filePath":"modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/agenda/AbstractAgenda.java","lineNumber":64,"sourceCode":"    }\n\n    @Override\n    public Runnable getNextOperation() {\n        assertOperationsNotEmpty();\n        if (!operations.isEmpty()) {\n            return operations.poll();\n        } else {\n            // If there are no more operations then we need to wait until any of the schedule future operations are done\n            List<ExecuteFutureActionOperation<?>> copyOperations = new ArrayList<>(futureOperations);\n            futureOperations.clear();\n            Duration maxWaitTimeout = getFutureMaxWaitTimeout();\n            return new WaitForAnyFutureToFinishOperation(this, copyOperations, maxWaitTimeout);\n        }\n    }\n\n    protected void assertOperationsNotEmpty() {\n        if (operations.isEmpty() && futureOperations.isEmpty()) {\n            throw new FlowableException(\"Unable to peek empty agenda.\");\n        }\n    }\n\n    /**\n     * Generic method to plan a {@link Runnable}.\n     */\n    @Override\n    public void planOperation(Runnable operation) {\n        operations.add(operation);\n        if (LOGGER.isDebugEnabled()) {\n            LOGGER.debug(\"Operation {} added to agenda\", operation.getClass());\n        }\n    }\n\n    @Override\n    public <V> void planFutureOperation(CompletableFuture<V> future, BiConsumer<V, Throwable> completeAction) {\n        ExecuteFutureActionOperation<V> operation = new ExecuteFutureActionOperation<>(future, completeAction);\n        if (future.isDone()) {","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/agenda/AbstractAgenda.java#L46-L82","documentation":"Thrown by AbstractAgenda.assertOperationsNotEmpty when getNextOperation is asked to peek at the next plan item while both the operations queue and futureOperations are empty. It is an internal engine invariant: callers should only take the next operation when work has actually been planned.","triggerScenarios":"Calling agenda.getNextOperation() after all planned operations have executed (e.g. test code or custom code driving the agenda manually and looping one iteration too many).","commonSituations":"Custom engine extensions or tests that drive command-context operations directly and mis-handle the end-of-agenda condition; usually indicates an engine-internal bug or incorrect low-level agenda usage rather than a user configuration problem.","solutions":["Check operations/futureOperations are non-empty (agenda.planOperation(...) was called) before calling getNextOperation.","In loops over the agenda, break out when operations are exhausted instead of calling getNextOperation again.","If hit during normal engine operation with no custom agenda code, report a bug with the process model and command stack."],"exampleFix":"// before\nwhile (true) { agenda.getNextOperation().run(); }\n// after\nwhile (!agenda.getOperations().isEmpty()) { agenda.getNextOperation().run(); }","handlingStrategy":"type-guard","validationCode":"if (agenda.getOperations().isEmpty() && agenda.getFutureOperations().isEmpty()) return; // nothing to execute","typeGuard":"boolean hasPendingWork(Agenda agenda) { return !agenda.getOperations().isEmpty() || !agenda.getFutureOperations().isEmpty(); }","tryCatchPattern":"try {\n  agenda.getNextOperation().run();\n} catch (FlowableException e) {\n  if (\"Unable to peek empty agenda.\".equals(e.getMessage())) return; // agenda drained\n  throw e;\n}","preventionTips":["Only drive the agenda loop while operations are planned.","Avoid custom code calling getNextOperation directly; use the engine's operation execution APIs.","Treat this as an internal invariant bug if it appears without custom agenda usage."],"tags":["agenda","internal","empty-collection"],"backgroundTag":"empty-required-field","analyzedSha":"d6d39ce1c69ff244f2d9dc6af756a9b95e865586","analyzedAt":"2026-09-11T06:41:19.413Z","contentChangedAt":"2026-09-11T06:41:19.413Z","schemaVersion":2},"datasetVersion":"2026-09-18T11:17:12.947Z"}