Commit 791bc3ff7b for frr

commit 791bc3ff7b27adcf793acb751a6115fa1b984792
Author: Rafael Zalamena <rzalamena@opensourcerouting.org>
Date:   Fri Oct 9 16:52:30 2026 +0000

    tools: fix frr-reload "no" retry inside a context

    This commit fixes two issues:

    1. `original_cmd` was pointing to `cmd`, so when the loop was entered it
       would alter the original command that failed.
    2. `lines_to_config()` last command is always "exit" so the loop would
       give up on the first try.

    Since commit 792ada4738 ("tools: always append "exit" in frr-reload.py")
    `lines_to_config()` closes every context it opens with "exit", but the
    retry still trimmed the last line (`last_arg` was always "exit" and
    `len(["exit"])` was always `<= 2`).

    The retry now trims the last line before the "exit" lines. Example, when
    vtysh rejects:

    ```
    interface eth0
     no ip ospf authentication message-digest 1.1.1.1
    exit
    ```

    The next attempt is:

    ```
    interface eth0
     no ip ospf authentication message-digest
    exit
    ```

    And so on, one word at a time, until vtysh accepts it or only
    "no WORD" is left.

    Signed-off-by: Rafael Zalamena <rzalamena@opensourcerouting.org>

diff --git a/tools/frr-reload.py b/tools/frr-reload.py
index d3eedc56c1..f08cadd80b 100755
--- a/tools/frr-reload.py
+++ b/tools/frr-reload.py
@@ -2795,9 +2795,18 @@ def delete_line_with_vtysh(vtysh, ctx_keys, line):
     reload.
     """
     cmd = lines_to_config(ctx_keys, line, True)
-    original_cmd = cmd
     vrf_ctx_del = is_vrf_context_delete(ctx_keys, line)

+    # Create a copy of list `cmd` instead of pointing to `cmd`, because
+    # it will be modified later in the loop below.
+    original_cmd = list(cmd)
+
+    # `lines_to_config()` closes every context it opens with "exit", so the
+    # command to trim is the last line that is not an "exit".
+    cmd_index = len(cmd) - 1
+    while cmd_index > 0 and cmd[cmd_index].strip() == "exit":
+        cmd_index -= 1
+
     while True:
         try:
             vtysh(["configure"] + cmd)
@@ -2828,17 +2837,18 @@ def delete_line_with_vtysh(vtysh, ctx_keys, line):
             # - Split that last entry by whitespace and drop the last word
             log.error("Failed to execute %s", " ".join(cmd))
             log.error("%s", e)
-            last_arg = cmd[-1].split(" ")
+            no_line = cmd[cmd_index]
+            indent = no_line[: len(no_line) - len(no_line.lstrip())]
+            words = no_line.split()

-            if len(last_arg) <= 2:
+            if len(words) <= 2:
                 log.error(
                     '"%s" we failed to remove this command',
                     " -- ".join(original_cmd),
                 )
                 return False

-            new_last_arg = last_arg[0:-1]
-            cmd[-1] = " ".join(new_last_arg)
+            cmd[cmd_index] = indent + " ".join(words[:-1])
         else:
             # Success is not logged here: lines_to_del is logged upfront before
             # deletes run (see reload path), matching ADD's avoid-double-log.