I’m a long time n8n user. Using it since 2020. I have so many workflows which we’re not updated since I created them.
That time we request the n8n team to remove the n8n start node. Finally they did after long time.
Recently I updated my n8n instance to v2. Guess what. Before updating to the newer version, I was supposed to remove all the start nodes. Which I never did.
When I restarted the n8n container, I started getting “Unrecognized node type: n8n-nodes-base.start” error.
I don’t have access to Web UI. So I can’t remove it.
So I wrote a bash script which pulls all the workflows, remove the start node code and patch it.
This only works if you have n8n running on Docker.
Save the below code with the filename anywhere on your Linux machine.
fix_start_nodes.sh you’ll need your n8n container name. In my case it’s n8n-n8n_main
Then you have to run ./fix_start_nodes.sh n8n-n8n_main-1 the script will patch everything.
Once done. Restart the container, Restart the Webserver and you’re good to go.
#!/usr/bin/env bash
#
# fix_start_nodes.sh
#
# Full auto version. For every n8n workflow still containing the legacy
# "n8n-nodes-base.start" node (removed in n8n v2):
# 1. Exports all workflows, finds the broken ones.
# 2. For any broken workflow currently ACTIVE, stops the container and
# flips it to active=0 directly in the SQLite DB (so n8n won't try
# to parse the broken node while deactivating during import).
# 3. Restarts, patches each broken workflow's Start node -> Manual
# Trigger, and re-imports.
# 4. Stops the container again, flips the originally-active ones back
# to active=1 in the DB.
# 5. Restarts n8n so it activates them cleanly using the fixed nodes.
#
# Requires on HOST: jq, python3 (both usually preinstalled on Ubuntu).
# Requires in CONTAINER: n8n CLI (already there).
#
# Usage:
# ./fix_start_nodes.sh [container_name] [sqlite_path_in_container]
#
# Defaults:
# container_name = n8n-n8n_main-1
# sqlite_path_in_container = /home/node/.n8n/database.sqlite
set -euo pipefail
CONTAINER="${1:-n8n-n8n_main-1}"
DB_PATH_IN_CONTAINER="${2:-/home/node/.n8n/database.sqlite}"
WORKDIR="$(mktemp -d)"
EXPORT_DIR_IN_CONTAINER="/tmp/n8n_export"
echo "==> Container: ${CONTAINER}"
echo "==> DB path (in container): ${DB_PATH_IN_CONTAINER}"
echo "==> Scratch dir: ${WORKDIR}"
for bin in jq python3 docker; do
if ! command -v "$bin" >/dev/null 2>&1; then
echo "ERROR: '$bin' not found on host. Please install it."
exit 1
fi
done
if ! docker ps -a --format '{{.Names}}' | grep -qx "${CONTAINER}"; then
echo "ERROR: container '${CONTAINER}' not found."
docker ps -a --format ' - {{.Names}}'
exit 1
fi
# Make sure container is running for the initial export.
if ! docker ps --format '{{.Names}}' | grep -qx "${CONTAINER}"; then
echo "==> Container not running, starting it..."
docker start "${CONTAINER}"
sleep 5
fi
echo "==> Exporting all workflows..."
docker exec "${CONTAINER}" sh -c "rm -rf ${EXPORT_DIR_IN_CONTAINER} && mkdir -p ${EXPORT_DIR_IN_CONTAINER}"
docker exec "${CONTAINER}" n8n export:workflow --all --separate --output="${EXPORT_DIR_IN_CONTAINER}"
docker cp "${CONTAINER}:${EXPORT_DIR_IN_CONTAINER}" "${WORKDIR}/export"
BROKEN_IDS=()
BROKEN_ACTIVE_IDS=()
for f in "${WORKDIR}/export"/*.json; do
[ -e "$f" ] || continue
HAS_START=$(jq '[.nodes[]? | select(.type == "n8n-nodes-base.start")] | length' "$f")
[ "$HAS_START" -eq 0 ] && continue
WF_ID=$(jq -r '.id' "$f")
WF_NAME=$(jq -r '.name' "$f")
WF_ACTIVE=$(jq -r '.active' "$f")
echo "==> Broken: \"${WF_NAME}\" (ID: ${WF_ID}, active: ${WF_ACTIVE})"
BROKEN_IDS+=("$WF_ID")
if [ "$WF_ACTIVE" = "true" ]; then
BROKEN_ACTIVE_IDS+=("$WF_ID")
fi
# Patch the node type in place, keep everything else.
START_NAMES=$(jq -r '[.nodes[] | select(.type == "n8n-nodes-base.start") | .name] | .[]' "$f")
TMP="${f}.tmp.json"
cp "$f" "$TMP"
while IFS= read -r START_NAME; do
[ -z "$START_NAME" ] && continue
jq --arg name "$START_NAME" '
.nodes = [
.nodes[] |
if .name == $name then
.type = "n8n-nodes-base.manualTrigger"
| .typeVersion = 1
| .parameters = {}
else . end
]
' "$TMP" > "${TMP}.out" && mv "${TMP}.out" "$TMP"
done <<< "$START_NAMES"
mv "$TMP" "$f"
done
if [ "${#BROKEN_IDS[@]}" -eq 0 ]; then
echo "==> No workflows with legacy Start node found. Nothing to do."
exit 0
fi
echo ""
echo "==> Total broken: ${#BROKEN_IDS[@]} | Currently active (need DB toggle): ${#BROKEN_ACTIVE_IDS[@]}"
# ---- Step: if any broken workflow is active, deactivate it directly in the DB ----
if [ "${#BROKEN_ACTIVE_IDS[@]}" -gt 0 ]; then
echo "==> Stopping container to safely patch SQLite DB..."
docker stop "${CONTAINER}"
echo "==> Copying DB out to host..."
docker cp "${CONTAINER}:${DB_PATH_IN_CONTAINER}" "${WORKDIR}/database.sqlite"
ID_LIST=$(printf "'%s'," "${BROKEN_ACTIVE_IDS[@]}")
ID_LIST="${ID_LIST%,}" # trim trailing comma
echo "==> Setting active=0 for IDs: ${BROKEN_ACTIVE_IDS[*]}"
python3 - "$WORKDIR/database.sqlite" "$ID_LIST" <<'PYEOF'
import sqlite3, sys
db_path, id_list = sys.argv[1], sys.argv[2]
conn = sqlite3.connect(db_path)
sql = f"UPDATE workflow_entity SET active = 0 WHERE id IN ({id_list})"
cur = conn.execute(sql)
conn.commit()
print(f"Rows updated: {cur.rowcount}")
conn.close()
PYEOF
echo "==> Copying patched DB back into container..."
docker cp "${WORKDIR}/database.sqlite" "${CONTAINER}:${DB_PATH_IN_CONTAINER}"
echo "==> Starting container..."
docker start "${CONTAINER}"
sleep 5
fi
# ---- Step: copy fixed workflow JSON back in and re-import ----
echo "==> Copying fixed workflows back into container..."
docker cp "${WORKDIR}/export" "${CONTAINER}:${EXPORT_DIR_IN_CONTAINER}_fixed"
echo "==> Re-importing fixed workflows..."
docker exec "${CONTAINER}" n8n import:workflow --separate --input="${EXPORT_DIR_IN_CONTAINER}_fixed"
# ---- Step: reactivate the ones that were originally active ----
if [ "${#BROKEN_ACTIVE_IDS[@]}" -gt 0 ]; then
echo "==> Stopping container to re-activate fixed workflows in DB..."
docker stop "${CONTAINER}"
docker cp "${CONTAINER}:${DB_PATH_IN_CONTAINER}" "${WORKDIR}/database2.sqlite"
ID_LIST=$(printf "'%s'," "${BROKEN_ACTIVE_IDS[@]}")
ID_LIST="${ID_LIST%,}"
echo "==> Setting active=1 for IDs: ${BROKEN_ACTIVE_IDS[*]}"
python3 - "$WORKDIR/database2.sqlite" "$ID_LIST" <<'PYEOF'
import sqlite3, sys
db_path, id_list = sys.argv[1], sys.argv[2]
conn = sqlite3.connect(db_path)
sql = f"UPDATE workflow_entity SET active = 1 WHERE id IN ({id_list})"
cur = conn.execute(sql)
conn.commit()
print(f"Rows updated: {cur.rowcount}")
conn.close()
PYEOF
docker cp "${WORKDIR}/database2.sqlite" "${CONTAINER}:${DB_PATH_IN_CONTAINER}"
echo "==> Starting container (final)..."
docker start "${CONTAINER}"
fi
echo ""
echo "==> Done. Fixed workflow IDs: ${BROKEN_IDS[*]}"
echo "==> Re-activated (were active before): ${BROKEN_ACTIVE_IDS[*]:-none}"
echo ""
echo "==> Tail logs to confirm clean activation:"
echo " docker logs -f ${CONTAINER}"