Fix wolf.sh update_field(): scope field search to the app's own block

update_field() searched a blind +/-25-line window around an app's
name = '...' line to find and rewrite its mounts/env field. Once app
blocks got shorter (e.g. after collapsing a 3-line mounts array into
one line), two adjacent apps' blocks could end up close enough that
the window reached into a neighboring app's block instead — splicing
that block's own field or, worse, eating into its
[profiles.apps.runner] table header.

Confirmed live: this corrupted config.toml into invalid TOML and
crash-looped Wolf outright:

  ERROR | Unhandled exception: Error while parsing table header:
  cannot redefine existing table 'profiles.apps.runner'

Fix: bound the search to the enclosing [[profiles.apps]] block only —
walk backward from the name line to the nearest [[profiles.apps]]
header, forward to the next [[profiles.apps]] or [[profiles]] header,
and only look for the field within that range. Never crosses into a
neighboring block regardless of how short either one is.
This commit is contained in:
Claude
2026-08-31 20:45:18 +00:00
parent c1a97d8945
commit ed939e5826
+32 -6
View File
@@ -1421,14 +1421,27 @@ def update_field(lines, wolf_name, field, new_value):
# any orphaned remnants left by a previously corrupted single-line rewrite.
for i, line in enumerate(lines):
if f"name = '{wolf_name}'" in line:
for j in range(max(0, i-25), min(len(lines), i+25)):
# Bound the search to just THIS app's own [[profiles.apps]] block —
# a blind +/-25-line window used to reach into a neighboring app's
# block once blocks got short enough (e.g. after collapsing a
# mounts array down to one line), splicing that block's own
# mounts/env field or table header instead. Confirmed live: this
# corrupted config.toml into invalid TOML ("cannot redefine
# existing table 'profiles.apps.runner'") and crash-looped Wolf.
block_start = i
while block_start > 0 and lines[block_start].strip() != '[[profiles.apps]]':
block_start -= 1
block_end = i + 1
while block_end < len(lines) and lines[block_end].strip() not in ('[[profiles.apps]]', '[[profiles]]'):
block_end += 1
for j in range(block_start, block_end):
if lines[j].lstrip().startswith(field + ' ='):
indent = lines[j][:len(lines[j]) - len(lines[j].lstrip())]
k = j
while k < len(lines) and ']' not in lines[k]:
while k < block_end and ']' not in lines[k]:
k += 1
m = k + 1
while m < len(lines):
while m < block_end:
s = lines[m].lstrip()
if s.startswith("'") or s.startswith(']'):
m += 1
@@ -2706,14 +2719,27 @@ def update_field(lines, wolf_name, field, new_value):
# any orphaned remnants left by a previously corrupted single-line rewrite.
for i, line in enumerate(lines):
if f"name = '{wolf_name}'" in line:
for j in range(max(0, i-25), min(len(lines), i+25)):
# Bound the search to just THIS app's own [[profiles.apps]] block —
# a blind +/-25-line window used to reach into a neighboring app's
# block once blocks got short enough (e.g. after collapsing a
# mounts array down to one line), splicing that block's own
# mounts/env field or table header instead. Confirmed live: this
# corrupted config.toml into invalid TOML ("cannot redefine
# existing table 'profiles.apps.runner'") and crash-looped Wolf.
block_start = i
while block_start > 0 and lines[block_start].strip() != '[[profiles.apps]]':
block_start -= 1
block_end = i + 1
while block_end < len(lines) and lines[block_end].strip() not in ('[[profiles.apps]]', '[[profiles]]'):
block_end += 1
for j in range(block_start, block_end):
if lines[j].lstrip().startswith(field + ' ='):
indent = lines[j][:len(lines[j]) - len(lines[j].lstrip())]
k = j
while k < len(lines) and ']' not in lines[k]:
while k < block_end and ']' not in lines[k]:
k += 1
m = k + 1
while m < len(lines):
while m < block_end:
s = lines[m].lstrip()
if s.startswith("'") or s.startswith(']'):
m += 1