I am sharing a painful episode I recently had.
Is this a bug? (repeated at the end, but anyway.)
Back story:
I have a NAS which is not always on.
When it is on, I want my 3 Pi's to back up their saved flows to it. (SMB)
Idea:
the NAS would be turned on, a check would see it, back up the files and all is good.
Problem 1:
I had to mount the share to allow things to happen.
SMB is not happy and I had to use fstab to get the share mounted.
Problem 2:
(A lot skipped because they are sort of outside the scope of what I'm sharing here/now)
rsync supports a --dry-run option.
If it is true then it only pretends to do it. If false then it is really done.
eg:
return /usr/bin/rsync ${dryRunOption}-avh --no-o --no-g "${d}" "${remoteBase}/"``
(Line 38 in the first function node)
I have a function node to build the array of commands and that is passed to another function node to split the array up and pass the commands to the second exec node.
(the first one built the arrays)
So - with GPT's help - the code was written and I would change a line in the function node to change the dry-run from true to false so things really worked.
They didn't.
I can't show you old code and didn't know the problem/s at the time.
Just when I found it I realised the problem.
exert from flow.
[
{
"id": "6093eee6e177df21",
"type": "function",
"z": "7e66ea1ffcc6c504",
"g": "ac2230dcc7d96f62",
"name": "Prepare rsync commands",
"func": "// Check for dryrun toggle message\nif (msg.topic === 'dryrun') {\n let dryRun = (msg.payload === true || msg.payload === 'true')\n context.set('dryRun', dryRun)\n node.status({ text: \"DryRun set to \" + dryRun })\n return null // exit early\n}\n\n// Normal processing of directory messages\n// Retrieve dryRun from node context\nlet dryRun = context.get('dryRun') // default undefined = simulate if not set\n\nnode.status({ text: \"DryRun \" + dryRun })\n\n// Split the output of 'find' into individual directories\nlet dirs = msg.payload.split('\\n').filter(s => s.trim() !== '')\n\n// Only include directories after this cutoff (YYYY-MM)\nconst cutoff = '2025-10'\n\n// Filter directories that are after the cutoff\nlet newDirs = dirs.filter(fullPath => {\n let dirName = fullPath.trim().split('/').pop() // get last part of path\n let yearMonth = dirName.substring(0, 7)\n return yearMonth >= cutoff\n})\n\n// Get the device name from global context\nconst myDevice = global.get(\"myDeviceName\")\n\n// Build rsync commands, including year folder dynamically\nmsg.rsynccmds = newDirs.map(d => {\n const dryRunOption = dryRun ? '--dry-run ' : ''\n const dirName = d.trim().split('/').pop() // e.g., \"2025-11-21.21.51.11\"\n const year = dirName.substring(0, 4) // \"2025\"\n const remoteBase = `/mnt/smb/NR/${myDevice}/${year}`\n\n return `/usr/bin/rsync ${dryRunOption}-avh --no-o --no-g \"${d}\" \"${remoteBase}/\"`\n})\n\nmsg.index = 0\nreturn msg\n",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 860,
"y": 920,
"wires": [
[
"e4d287f87bc55feb",
"59a0fd7f43066660"
]
]
},
{
"id": "e4d287f87bc55feb",
"type": "function",
"z": "7e66ea1ffcc6c504",
"g": "ac2230dcc7d96f62",
"name": "Run next rsync",
"func": "// Needed to indicate start/stop of sequence\nlet msg1 = {}\n// Stop if no more commands\nif (!msg.rsynccmds || msg.index >= msg.rsynccmds.length) {\n msg1.payload = 0\n return [null,msg1]\n}\n\n// Send current command to Exec node\nmsg.payload = msg.rsynccmds[msg.index]\nmsg1.payload = 1\nreturn [msg,msg1]",
"outputs": 2,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 630,
"y": 990,
"wires": [
[
"bc71410642c07a9a",
"16ad656e181e0b97"
],
[
"6cd51b56501cfa7c"
]
],
"outputLabels": [
"",
"start/stop for LED"
]
},
{
"id": "bc71410642c07a9a",
"type": "exec",
"z": "7e66ea1ffcc6c504",
"g": "ac2230dcc7d96f62",
"command": "",
"addpay": "payload",
"append": "",
"useSpawn": "false",
"timer": "",
"winHide": false,
"oldrc": false,
"name": "Run rsync",
"x": 810,
"y": 990,
"wires": [
[
"4974f7fe81962fe5",
"d3affa8f94a261ef"
],
[],
[]
]
},
{
"id": "d3affa8f94a261ef",
"type": "function",
"z": "7e66ea1ffcc6c504",
"g": "ac2230dcc7d96f62",
"name": "Next rsync",
"func": "msg.index++\nreturn msg",
"outputs": 1,
"x": 980,
"y": 1010,
"wires": [
[
"e4d287f87bc55feb"
]
]
},
{
"id": "f265fde7c6335fc7",
"type": "inject",
"z": "7e66ea1ffcc6c504",
"name": "Simulate",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "dryrun",
"payload": "true",
"payloadType": "bool",
"x": 600,
"y": 650,
"wires": [
[
"6093eee6e177df21"
]
]
},
{
"id": "32453af4e7ef9c48",
"type": "inject",
"z": "7e66ea1ffcc6c504",
"name": "Do",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "dryrun",
"payload": "false",
"payloadType": "bool",
"x": 590,
"y": 690,
"wires": [
[
"6093eee6e177df21"
]
]
}
]
So I would set/inject the DO trigger the flow and nothing would happen. TIME AND TIME AGAIN.
The entire command was sent as the payload.
eg:
Copied the command and did it FOR REAL and it would work.
WTF is going on?
/usr/bin/rsync -avh --no-o --no-g "/home/pi/Public/NR/2025-11-21.21.51.11" "/mnt/smb/NR/TimePi/2025/"
NO MENTION of --dry-run anywhere.
As a test, I put that command into a new exec node and tried.
It worked.
So this established that it could work.
What's going on?
Opened the second exec node. (from flow posted) (Force rsync) and saw this:
(simulated now)
Removed the true and left it blank.
ALL WORKING
Errr..... Bug?
I don't understand how it would force rsync to do a dry-run if that was set to true.
I can't see the link/association.
Some one?


