It's more a job for perl whose regexp can more easily find matching pairs. Anyway, the few sed implementations that have a -i option for in-place editing have copied it from perl and in ways incompatible between each other.
If we assume that {/} are always matched even inside "..." strings, that could be
perl -0777 -i -pe '
s{
^ \h* filter \s* ( \{
(?: [^{}]++ | (?1) ) *
\} )
}{
$& =~ s{ ^ \h* \K \# \h* (year_n \h* =) }{$1}gmrx
}gmxe' -- your-file
Where:
-penables thesedmode where files are processed one record at a time where the equivalent ofsed's pattern space is the$_variable (on whichs{pattern}{replacement}flagsoperates by default, likesed'ss).-ifor in-place editing (since copied by somesedimplementations).-0777changes the record separator from the default of newline (like insed) to some impossible byte value, so the files are processed as a whole (the slurp mode). Same as-gin newer versions ofperl.- then we have a
s{pattern}{replacement}gmxewhere:xallows adding whitespace (and comments) in the pattern to improve legibility.mmakes it so that^matches at the start of every line in the subject instead of just at the start of the subject.eis for thereplacementto beevaluated asperlcode.\sis for any whitespace (well ASCII only by default) including newline, similar to[[:space:]]in POSIX regexp, and\hfor horizontal whitespace (ISO8859-1 ones by default, that is space, tab, and non-breaking-space encoded as 0xA0, but importantly not newline; similar to POSIX'[[:blank:]]).++is like+but non-backtracking. Can help the matcher not get lost in a backtracking maze if there were unmatched{/}s.- The important part in there is
(?1)which recalls the regexp in the first(...)capture group so allows for recursive regexps. - The
replacementapplies anothers{pattern}{replacement}gmrxto$&which is what was matched by the first regexp with:rreturns the result of the substitution instead of applying it in place to$&\Kmarks the start of what's toKeep from the match, so we don't discard what's matched by what's to the left of it.
To find those files that have such matches so only those be edited, you can do something:
export REGEX='(?xm)
^ \h* filter \s* ( \{
(?: [^{}]++ | (?1) ) *
\} )'
find config_dir/ -type f -exec perl -0777 -ne '
print "$ARGV\0" if m{$ENV{REGEX}}' {} + |
xargs -r0 perl -0777 -i -pe '
s{$ENV{REGEX}}{
$& =~ s{ ^ \h* \K \# \h* (year_n \h* =) }{$1}mxgr
}ge' --
With sed, it's possible to find those matching pairs, but a lot more effort. One approach as seen for instance at my answer to removing braces statements containing nested braces inside is to replace the {/}s starting from the innermost ones in a loop using some escape mechanism until you can match filter{[^{}]*}.
Some notes about your attempt:
(?s) (which causes . to also match on newline, but your regexp has no .), (?0) (which recalls the whole regex so doesn't make sense here), \s, *? are all perl regexp operators.
By default grep and sed take Basic Regular Expressions, and Extended Regular Expressions with -E. A few grep implementations (such as GNU grep when built with optional PCRE2 support) can support perl-like regexps with a -P option, but very few sed implementations do.
Some grep and sed implementations support \s and/or *? with -E though. The latter is now specified by POSIX for extended regular expressions since the 2024 edition, but few implementations support it yet as of 2025.
No grep implementation that I know has a slurp mode that matches the regexp against the whole file, though pcre2grep comes close with its -M for multiline mode, and GNU grep with its -z to process NUL-delimiter records (text files are not meant to contain NULs). By default, like sed, they work on one line at a time. GNU sed also has -z for NUL-delimited records, or you can load the whole input into the pattern space programmatically in sed code with a -e :1 -e '$!{N;b1' -e '}' though beware some seds have a relatively low limit on the size of their pattern space.
xargs cannot handle arbitrary file paths unless you use the -0 option (now standard like -r since POSIX2024) on an input with NUL-delimited records.
With -I{}, it splits on unquoted newlines, removes trailing blanks, and handles some forms of quotes so would choke on file paths that contain those. It also means running one sed invocation for each file. You're also not using the {} place holder in the command.
It should be grep -rlZ ... | xargs -r0 sed ... (-Z like -r being a non-standard GNU extension).