Skip to content

[BUG] [v0.0.7] cortex-commands substitute_placeholders() replaces $1 before $10 — ascending-order loop corrupts two-digit placeholder indices into partial substitutions #53172

Description

@petar-vasilev

Project

cortex

Description

cortex-commands/src/command.rs substitute_placeholders() uses an ascending for-loop to replace numbered placeholders in the template:

for i in 1..=max_placeholder {
let placeholder = format!('${i}');
result = result.replace(&placeholder, &replacement);
}

Rust's str::replace() is a literal substring replacement. When i=1, it replaces all occurrences of '$1', INCLUDING the prefix '$1' inside '$10', '$11', '$12', ..., '$19'. Similarly, when i=2, it replaces '$2' inside '$20', '$21', etc.

Example: Template 'Run $1 and item $10', args = 'first second':

  1. i=1: replace '$1' with 'first'
    Template becomes: 'Run first and item first0' <- $10 CORRUPTED!
  2. i=10: replace '$10' in 'Run first and item first0'
    No '$10' found — it was already destroyed by i=1 step
    Final: 'Run first and item first0' (WRONG: expected 'Run first and item second')

Any command template using $10 or higher loses correct substitution. The last numbered argument also swallows extra args via 'captures rest' logic, but the index corruption happens before that step.

Error Message

Debug Logs

cortex-commands/src/command.rs:200-231 (substitute_placeholders):
    pub fn substitute_placeholders(template: &str, arguments: &str) -> String {
        let mut result = template.to_string();
        result = result.replace('$ARGUMENTS', arguments);
        let args: Vec<&str> = parse_arguments(arguments);
        let max_placeholder = find_max_placeholder(&result);

        for i in 1..=max_placeholder {  // BUG: ascending order!
            let placeholder = format!('${i}');
            let replacement = ...; // arg i or rest
            result = result.replace(&placeholder, &replacement); // BUG: corrupts $10+
        }
        result
    }

  Rust demonstration:
    let s = 'Run $1 and item $10';
    let s = s.replace('$1', 'alpha'); // => 'Run alpha and item alpha0'
    let s = s.replace('$10', 'kappa'); // No '$10' found! => 'Run alpha and item alpha0'

  cortex /multi-batch-cmd alpha beta gamma delta epsilon zeta eta theta iota kappa
    Expected: 'Process alpha in batch kappa'
    Actual:   'Process alpha in batch alpha0'
  error: template placeholder $10 was corrupted to 'alpha0' by $1 substitution

System Information

OS: Ubuntu 22.04 LTS  |  Version: v0.0.7

Screenshots

https://github.com/petar-vasilev/screenshots/blob/main/452b10bb87b6406cbb1068725ddba48e.png

Steps to Reproduce

  1. Run: cortex /custom-cmd arg1 arg2 arg3
  2. Create custom command template: 'Process $1 in batch $10'
  3. Run: cortex /custom-cmd alpha beta gamma delta epsilon zeta eta theta iota kappa
  4. Expected: 'Process alpha in batch kappa'
  5. Actual: 'Process alpha in batch alpha0' (because $10 became 'alpha' + '0')
  6. Verified with Rust: 'Process $1 in batch $10'.replace('$1', 'alpha') == 'Process alpha in batch alpha0'

Expected Behavior

substitute_placeholders() should replace placeholders in DESCENDING order
to avoid prefix collisions:

for i in (1..=max_placeholder).rev() { // Descending: $10 before $1
let placeholder = format!('${i}');
result = result.replace(&placeholder, &replacement);
}

With descending order for 'Run $1 and item $10':
i=10: replace '$10' with 'kappa' => 'Run $1 and item kappa'
i=1: replace '$1' with 'alpha' => 'Run alpha and item kappa'
Result: 'Run alpha and item kappa' (CORRECT)

Alternatively, use regex with word-boundary matching to avoid substring collision.

Actual Behavior

cortex-commands/src/command.rs: substitute_placeholders() iterates from i=1 to max_placeholder (ascending). At i=1, result.replace('$1', arg) also replaces the '$1' prefix within '$10', '$11', ..., '$19', converting them into 'arg10', 'arg11', etc. When i=10 is reached, '$10' no longer exists in the string, so the correct value for $10 is never inserted. Templates with 10+ positional placeholders silently produce wrong output.

Additional Context

── Code Evidence ────────────────────────────────────────────────────
This affects all templates using 2+ digit placeholders ($10 through $99).
The pattern of corruption:
- i=1 corrupts $10-$19 (substitutes $1 prefix)
- i=2 corrupts $20-$29 (substitutes $2 prefix)
- ...and so on

For example, with i=2 and arg2='beta':
'$21'.replace('$2', 'beta') = 'beta1' (not $21's intended value)

The find_max_placeholder() regex r'$(\d+)' correctly captures multi-digit
numbers, so max_placeholder may correctly be 10. But the loop order
then causes the corruption.

cortex-commands/src/command.rs:245-256 (find_max_placeholder)
fn find_max_placeholder(template: &str) -> u32 {
re.captures_iter(template) // correctly finds $10
.filter_map(...)
.max()
.unwrap_or(0) // may return 10, 11, etc.
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingideIssues related to IDEvalidValid issue

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions