Script failure to assign return as variable - manual command works - script engine bug?

Hi there. I’m new, this is my first post, AND ALSO I have in fact read the suggested information regarding scripting tips and tricks, basics, and so forth. Yes I understand that every idiot with a first post claiming there’s a bug in the OS, also says that. (PS; "Read before creating yet another new my script does not work topic" provides a link to the Wiki’s Scripting_Tips_and_Tricks page which does not exist.)

I’ve also posted this to the Mikrotik subreddit, despite several days and 2,000+ views, nobody has answered.

I assure you, this is not the standard newbie “script don’t work” post. Please consider this framing before you reply.

BACKGROUND:

I’d like to implement tiers of priority hosts on my network. On IPv4 I have done so using several Address Lists which categorize traffic into the Queue Tree. We’ll focus on interacting with just one, called PRIORITY_HOSTS. I have firewall mangle rules to tag packets based on those address lists. I have a Queue Tree implemented with relevant tag-to-queue rules etc. IPv4 works flawlessly, of course, but IPv6 is a lot more difficult to deal with here. I want to implement the same Address List rules on IPv6, using an Address List generated from a script.

Here’s the idea, using some random IPs in 192.168.0.0/16:

[Variably8919@MikroTik] > /ip/firewall/address-list/print where list=PRIORITY_HOSTS 
Columns: LIST, ADDRESS, CREATION-TIME
# LIST            ADDRESS          CREATION-TIME      
0 PRIORITY_HOSTS  192.168.1.125    2025-10-17 11:13:27
1 PRIORITY_HOSTS  192.168.25.156   2025-10-17 11:13:28
2 PRIORITY_HOSTS  192.168.25.222   2025-10-17 11:13:29
3 PRIORITY_HOSTS  192.168.255.252  2025-10-17 11:13:30
4 PRIORITY_HOSTS  192.168.3.244    2025-10-17 11:13:31

Intent:

  • Read IPv4 Address List (Working)
  • Lookup MAC via IP from DHCP Leases (Working)
  • Lookup IPv6 Address via MAC from ND (Working)
  • Write IPv6 Address List (Working but Problematic)

THE ACTUAL PROBLEM:

In the IPv6 Address List, I need to ADD an entry if it doesn’t exist, but SET an entry if it does exist. I have written a clause to test if the entry exists, but the test does not work and everything I can see says that it should be working.

The commands I used BY HAND via Terminal, work properly, but when modified to run in the context of a script, one command has no return value WHEN RUN AS A SCRIPT, but when run BY HAND it works and returns a value.

I understand that many commands are expected to behave in a manner similar to this but not in the context I mean.

The script itself provides commands to run to verify operation, and relevant commands return expected values (including the object reference which is not directly usable but proves there IS a return when run by hand.)

Please see the relevant script, which has comments to indicate where/how failures occur etc;

:log info "Start"
:foreach idx in=[/ip/firewall/address-list/find list=PRIORITY_HOSTS] do={
    :local ip [/ip/firewall/address-list/get $idx address];
    :local tag [/ip/firewall/address-list/get $idx comment];
    :local lease [/ip/dhcp-server/lease/find where address=$ip];
    :local mac [/ip/dhcp-server/lease/get $lease mac-address];
 
    :foreach n in=[/ipv6/neighbor/find where mac-address=$mac interface=BRIDGE_LAN] do={
        :local candidate [/ipv6/neighbor/get $n address]
        :log info [:serialize value=$candidate to=json]
        :if ([:len $candidate] > 0 && [:pick $candidate 0 4] = "2605") do={
 
            :log info ("/ipv6/firewall/address-list/print where list=PRIORITY_HOSTS address=" . $candidate);
            # ^^^ IF I COPY THIS COMMAND FROM LOGS AND RUN IT, THE RETURN INDICATES A VALID MATCH
 
            :log info (":put [/ipv6/firewall/address-list/find list=PRIORITY_HOSTS address=" . $candidate . "]");
            # ^^^ IF I COPY THIS COMMAND FROM LOGS AND RUN IT, THE RETURN INDICATES A VALID MATCH
 
            :local existing [/ipv6/firewall/address-list/find list=PRIORITY_HOSTS address=$candidate];
            # ^^^ NEVER POPULATED EVEN THOUGH LIST ENTRY IS 100% VERIFIED TO EXIST AND BOTH PRINT AND FIND COMMANDS 100% RETURN A MATCH MANUALLY
 
            :if ([:len $existing] = 0) do={
                /ipv6/firewall/address-list/add comment=$tag list=PRIORITY_HOSTS timeout=1:0:0 address=$candidate;
                # ^^^ ALWAYS THROWS ERROR AFTER FIRST RUN BECAUSE ENTRY EXISTS
            } else={
                /ipv6/firewall/address-list/set $existing timeout=4:0:0;
                # ^^^ NEVER RUNS BECAUSE `existing` IS NOT POPULATED
            }
        }
    }
}
:log info "End"

So, somehow populating a local variable, from the return of /ipv6/firewall/address-list/find list=PRIORITY_HOSTS address=$candidate does not work, as such a check to see if the entry exists fails, and I cannot ‘set’ an entry which already exists. I have checked, and $candidate IS populated (thus printing into the log) and at run-time $candidate is of type ip6 AND I have attempted to coerce it with :tostr and the resulting variable still does not populate $existing variable.

I am NOT interested in using Timeout to self-remove these entries. I’ve examined this as a fix but it will not fit our needs.

I think this is a script processing bug, but I am uncertain how to proceed.

Can anyone provide some feedback? Please consider the care I went through to justify the question and reinforce that I’ve taken steps to prove my case.

And yet timeout parameter is set in script when creating/updating list entry. What is set command supposed to update if timeout should not be used?

:if ([:len $existing] = 0) do={
/ipv6/firewall/address-list/add comment=$tag list=PRIORITY_HOSTS timeout=1:0:0 address=$candidate;

} else={
/ipv6/firewall/address-list/set $existing timeout=4:0:0;

}

on add command it is set expiration timeout for 1m and after that time list record will be deleted, so on next execution find command will not find any such records after 1 min and script will perform add command again due to condition :if ([:len $existing] = 0)

This was probably working in Terminal because commands are executed within 1 min while records in list still exists.

Remove timeout parameter on add command and set should be working, but idk what you need to update with set if you claim “I am NOT interested in using Timeout to self-remove these entries”. If you want to update list with new fresh addresses in list you need to remove all list entries tagged by comment before :foreach loop and use only add command.

P.S.

You should not rely on comments written in topic 7 years ago, things change… And you should better look address lists help for your case → Address-lists - RouterOS - MikroTik Documentation (Note for readers in the future: this link may not be valid in your time :slight_smile:)

Timout is set because because I want clients to fall off if the client does not return to the network after a time. When they return they will likely have a different IPv6 address, and I don’t want to have to manually prune old entries. Timeout allows them to fall off gracefully. I should have been more clear – Using timeout to self-remove the entries for the purpose of allowing this script to operate is not viable. I explicitly explored this; when Timeout is set, if the count of clients changes, the seconds worth of offset per-iteration per-client changes, which means sometimes a client will be updated late OR attempt to update several seconds EARLY which causes a script error. Timeout is not viable to be used as a self-pruning method for a looping script if you don’t want gap times in your actions.

You’re here to help “and yet” you’re starting out with confrontational language, trying to poke holes in my explanation before you’ve even taken time to understand the problem or the question.

You’ve misread the script, and every assumption you made from here out was invalid. On add it is set expiration timeout for 1 HOUR ( note: 1:0:0 is one hour. 0:1:0 is one minute. 0:0:1 is one second. For the future.) On SET, it is set for 4 HOURS. (4:0:0 is four hours, 0:4:0 is four minutes, 0:0:4 is one minute.)

The entire rest of that comment is completely invalidated by your misread.

Also you’re assuming I set a repeat timer for one minute, and assuming a lot of other things – stop doing that, you’re wasting your own time when you’re wrong. And, you’re wrong. I ran the script, BY HAND, with anywhere from ten seconds to twenty minutes between runs, because I was debugging and chasing it for several hours before coming to make this post.

You’re definitely wrong. It was one hour, and I had a Winbox window up literally looking at the records as I ran the commands. The script-executed commands and my commands are operating while the same entries are present. I even used static, non-timeout entries to test, but didn’t think that was relevant as I pretty heavily explained exactly where the failure is. I didn’t expect to be litigating my script, I expected someone with the experience to understand it. If you don’t want to help, go away, but nitpicking the part of my script that DOES WORK doesn’t help with the problem. Stop assuming whoever you’re talking to is inept, it makes you look really bad.

Did you test a SINGLE COMMAND or just speculate about the issues based on your misread assumptions of how it works?

Removing the timeout parameter will not fix the problem, which is exactly what I identified. I explained why using Timeout to self-prune this list is not viable above. The timeout exists for a separate reason which you do not get to dictate the appropriateness of. The timeout is not the problem, right now your misunderstanding regarding the script’s function, timing, and operatioin is stopping you from providing valuable help.

…first off. set is explicitly for updating entries. What I need to update, is the several-day timeout, so they don’t fall off after several days of inactivity – that’s how inactivity timers work. It’s a pretty basic principal. Same as a watchdog timer. Maybe you’ve heard of those? PIR controlled motion sensitive lights, how when you wave your hand they reset the time, they don’t turn off then back on every time you move, unless you fail to move in time, then you move AFTER it turns off and they turn back on? Same deal.

1 - Start timer
2 - Reset timer
3 - If timer hasn’t reset and runs out, do something.

I’m not going to litigate the paradigm of resetting a timeout entry with you; the timeout is load-bearing and not part of the problem.

The entire purpose of the set command is to NOT have to remove and re-create an entry. Your claim is completely ridiculous. If you don’t want to be here, feel free to leave. I’m not keeping you here, and this behavior isn’t helpful.

Yeah, I didn’t read the date. The post was linked somewhere, I thought in an FAQ or such but I forget how I found it.

And yes, no joke things change, thanks for the super well-meaning helpful comment, it’s really remarkable of you.

I also never claimed to “rely on comments written in topic 7 years ago” – I clicked THE LINK, IN THE MAIN POST from the topic written 7 years ago, and found that it was not working. Your phrasing is intentional, and it’s rude, and you don’t have to be here. I specifically mentioned it to show that I DID try digging, and searching, to try to find my solution. You’re being a mocking jerk, because I showed that I had tried to dig far and wide. Please reconsider why you’re here. It’s replies like this which prevent people from reaching out for help.

I’m familiar with it, because I read it multiple times while reading through all of the language documentation to write the MANY scripts I’ve written for my router over the last year of really getting into it, but while we’re at it, maybe you should read the documentation on set. It’s tricky though; it’s not just one page, and it has contextual meaning and application; when running in a script, it accepts a reference variable as its target instead of an index.

Yes, a regular comedian. How great of you to bring levity with no other purpose at all. Clever.

I have no will to dissect your response, ok I misread timeout value, but you did not explain logic behind these timeout values in 1st post so claim “I am NOT interested in using Timeout to self-remove these entries“ was confusing.

Anyway, best of luck in future effort to resolve this, not sure how much help will you get here with that charming personality, probably same as on Reddit. Red flag was already there - “Yes I understand that every idiot…“, I was idiot when trying to help…

Yeah, I understand – your type is only good for one reply, pushback breaks the facade and exposes that you don’t know the actual answer, you were just replying. Thank you, please leave.

Yup, sorry about that, hate to have wasted so much of your valuable time tearing apart the premise and not addressing the problem.

If you don’t like the mirror, don’t make faces at it. I’ll be perfectly charming to folks who approach with respect.

Yes but you didn’t need to be, you could have approached what I identified as the problem instead of trying to poke holes in my logic to force the perspective of the problem to change. You picked that approach, if that’s the idiot’s approach then please select something else next time.

Interesting topic, but I think it will be safer to leave.

I think @optio is just trying to be helpful. Part of being helpful is suggesting a simpler approach.

Best thing to do is add more logging/debugging code and/or try the various parts separately, especially paying attention to datatypes like lists of 1 item and using unquoted =$variable unless you're sure it's type is correct. Now it's totally possible one of the commands does something "unusual" from RouterOS usual schemes, but some more logging/checking code would confirm where and what. Easier to add a half dozen lines, to check is kinda my suggestion than argue...

To me this seems like a better approach, I think this comes from that there is less script code needed, and thus avoids need to have more complex find/get scripts. Similarly the why it doesn't meet needs is unknown as there is potentially a solution.

Thank you for taking the time to reply. Honestly, if that’s the case, I sincerely apologize to @optio. It came off entirely differently to me, but if I misread intent, I am genuinely sorry. I’ll admit to being really tired of “you don’t want X so nobody will help you fix X, instead you have to hear about what everyone else thinks you should do” and probably having gone off at least in part due to that.

Speaking of which;

As explained above, and I forgive anyone for not reading the whole thing, this is not a viable option for many reasons. I didn’t include every datapoint of my reasoning because I felt like it would be hammering my point home in justifying my needs, when I’d rather focus on the actual problem.

On principal: I intend to write many scripts, and I would rather learn how to accomplish my goal than just do a dumber script. I write complexity to the level of what I need. This example script is not the final script, and it is not the only script I am writing right now, or in the future, which will rely on the concepts at which I am failing now.

I don’t need an alternative.

I did exactly this process, which is what led me here. I explained in the script using comments exactly the line which does not work, and I reached that conclusion by using extensive debugging commands. I didn’t include a giant script with a dozen debug commands because more code would have made the problem less evident.

Due to the complexity of the failure involving trying to assign the return of a command which searches for a member on the network, this was the minimum viable script I could write to fully encompass “a proof” of sorts. I can try to write a more simplified script which reproduces the error without needing a literal live network to test on.

I don’t need help discovering a workaround. I need help understanding where the failure is, and learning the correct solution.

Very helpful. Thank you, that’s the most technical thing I’ve been able to find in approaching help. I believe I understand the type to be correct, as in other testing, using :typeof on the return of the manual command indicated ‘ip6’ as the type, and so I attempted to coerce the variable within the script to a string type (and verified that it did, in fact, change types AND contain the correct information, using debug prints) and had no success.

This seems like where I should focus. Can you quote my specific line, and provide a statement using a variable which is quoted? I speak several programming languages, and quoting rules are a weak spot.

Anyway... my basic suggestion was to add more logging, including :typeof $variable in output.

I don't see anything the screams wrong, and since your script depends on lists/etc to be setup it's not easy to test. But since it's seem the one line that's not doing what's expected is this one, which is where other things fail, focusing on that one seems to be good place to start:

:local existing [/ipv6/firewall/address-list/find list=PRIORITY_HOSTS address=$candidate];
            # ^^^ NEVER POPULATED EVEN THOUGH LIST ENTRY IS 100% VERIFIED TO EXIST AND BOTH PRINT AND FIND COMMANDS 100% RETURN A MATCH MANUALLY
 

So one suggestion is to add logging like :log info "address has type $[:typeof $candidate].

RouterOS variables have types. The specifics on what gets "cast" where gets tricky, and since find returns an array those can be tricky since you may need to use ->0. Similarly using quotes around things will force a string cast, and most commands can take string arguments. But some more log lines, perhaps at each step give you more clues.