{"record":{"id":"44469b3e05a776c8","repo":"nathanmarz/storm","slug":"failed-a-non-existent-or-already-acked-failed-id-id","errorCode":null,"errorMessage":"Failed a non-existent or already acked/failed id: ${id}","messagePattern":"Failed a non-existent or already acked/failed id: (.+?)","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"critical","filePath":"storm-core/src/jvm/backtype/storm/task/ShellBolt.java","lineNumber":202,"sourceCode":"        _running = false;\n        _process.destroy();\n        _inputs.clear();\n    }\n\n    private void handleAck(Map action) {\n        String id = (String) action.get(\"id\");\n        Tuple acked = _inputs.remove(id);\n        if(acked==null) {\n            throw new RuntimeException(\"Acked a non-existent or already acked/failed id: \" + id);\n        }\n        _collector.ack(acked);\n    }\n\n    private void handleFail(Map action) {\n        String id = (String) action.get(\"id\");\n        Tuple failed = _inputs.remove(id);\n        if(failed==null) {\n            throw new RuntimeException(\"Failed a non-existent or already acked/failed id: \" + id);\n        }\n        _collector.fail(failed);\n    }\n\n    private void handleError(Map action) {\n        String msg = (String) action.get(\"msg\");\n        _collector.reportError(new Exception(\"Shell Process Exception: \" + msg));\n    }\n\n    private void handleEmit(Map action) throws InterruptedException {\n        String stream = (String) action.get(\"stream\");\n        if(stream==null) stream = Utils.DEFAULT_STREAM_ID;\n        Long task = (Long) action.get(\"task\");\n        List<Object> tuple = (List) action.get(\"tuple\");\n        List<Tuple> anchors = new ArrayList<Tuple>();\n        Object anchorObj = action.get(\"anchors\");\n        if(anchorObj!=null) {\n            if(anchorObj instanceof String) {","sourceCodeStart":184,"sourceCodeEnd":220,"githubUrl":"https://github.com/nathanmarz/storm/blob/cdb116e942666973bc4eaa0df098d5bab82739e7/storm-core/src/jvm/backtype/storm/task/ShellBolt.java#L184-L220","documentation":"ShellBolt runs a shell subprocess and keeps a map (_inputs) of pending tuple ids awaiting ack/fail from the shell process. When the shell sends a 'fail' action for an id that is not in that map — because it was already acked, already failed, or never emitted — Storm throws this RuntimeException, which kills the executor thread and typically the worker.","triggerScenarios":"The multilang shell process sends a fail action referencing a tuple id that was already acked or failed, or an id that was never emitted to it (e.g. the shell script fabricates ids or double-reports the same tuple).","commonSituations":"Non-idempotent ack/fail logic in custom shell spout/bolt scripts (python/node/ruby), multilang scripts that ack and then fail the same tuple on error, or scripts emitting their own made-up ids after a crash/restart of their internal state.","solutions":["Fix the shell script so each tuple id is acked or failed exactly once (track state, never double-report).","Ensure the script only fails ids it actually received via the input stream (echo ids received in __init/next).","On error paths, choose a single terminal action (ack OR fail) per tuple and return immediately after.","Upgrade Storm if you suspect a race between ack and fail being sent; keep ack/fail messages ordered.","Log every ack/fail id in the script during debugging to find the duplicate or bogus id."],"exampleFix":"# before (script may fail after ack)\ntry:\n    process(line)\n    ack(msg['id'])\nexcept Exception:\n    fail(msg['id'])\n\n# after (track terminal state)\ndone = set()\ndef safe_fail(tid):\n    if tid in done:\n        return\n    done.add(tid)\n    fail(tid)","handlingStrategy":"validation","validationCode":"pending = set()\n# on receiving a tuple from Storm:\npending.add(msg_id)\n# before sending fail:\nif msg_id in pending:\n    send_fail(msg_id)\n    pending.discard(msg_id)","typeGuard":"def is_pending(msg_id):\n    return msg_id in pending","tryCatchPattern":null,"preventionTips":["Ack or fail each tuple exactly once; use a pending set to track ids.","Never fabricate tuple ids inside the shell script.","Fail fast in the script on error paths after exactly one terminal action.","Test multilang scripts with duplicate ack/fail scenarios before deployment."],"tags":["storm","multilang","shell-bolt","tuple-lifecycle"],"backgroundTag":"invalid-state-transition","analyzedSha":"cdb116e942666973bc4eaa0df098d5bab82739e7","analyzedAt":"2026-09-12T14:30:00.714Z","contentChangedAt":"2026-09-12T14:30:00.714Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}