Skip to content

History ranges starting past the end select and replay the last command #1777

Open

Description

@AryanRG

When a history range starts after the last entry, it selects the last command instead of
selecting nothing. With history --run, this silently executes that command again.

Environment

  • cmd2 source: main at 93cfe811c6a56015e3f0aec8d8e61c56faf9573c.
    Editable distribution metadata: 4.3.1.dev3+g93cfe811c.
  • CPython 3.13.12; macOS 27.0 arm64.
  • rich 15.0.0; rich-argparse 1.8.0; prompt-toolkit 3.0.53.

Reproduction

Run this against the source revision above:

import io

import cmd2


class App(cmd2.Cmd):
    def __init__(self):
        super().__init__(stdout=io.StringIO(), auto_load_commands=False)
        self.calls = []

    def do_mark(self, arg):
        self.calls.append(str(arg))


app = App()
app.onecmd_plus_hooks("mark first")
app.onecmd_plus_hooks("mark last")
print("before:", app.calls)
app.onecmd_plus_hooks("history --run 999:")
print("after:", app.calls)

Observed on unchanged source:

before: ['first', 'last']
after: ['first', 'last', 'last']

Expected: after: ['first', 'last'], since no entry has index 999 or greater.
There is no error or traceback; the final command is replayed silently.

The same boundary affects range selection without replay: with three entries,
History.span("4:") and History.span("999:1000") select entry 3 instead of returning
an empty mapping. Both : and .. range separators are affected.

Cause and proposed fix

History.span() clamps the converted start index to len(self) - 1, moving a start past
the end back onto the last entry. Allowing the start to reach len(self) yields an empty
slice while retaining the existing negative-index and inclusive-endpoint behavior.

A local prototype changes that clamp and adds range-boundary and command-replay regressions.
The same 14 new cases produced 10 failures and 4 passes on unchanged production source,
then 14 passes with the prototype. These are local results, not hosted CI results.

Searches of the open queue and relevant closed reports did not identify this specific silent
selection/replay behavior. This is separate from #1776, which concerns annotated help output.

AI assistance was used for the investigation, this report, and preparation and testing of
the local patch.

Activity

  1. AryanRG commented on Oct 17, 2026

    @AryanRG
    Author

    Work is underway on a focused fix for this issue. An unpublished local prototype changes
    the start-index clamp in History.span() and adds regression tests for ranges starting
    past the end, boundary behavior, and history --run, plus a changelog entry.

    The required quality, test, and documentation checks will be rerun before submitting a PR.
    AI assistance is being used for the patch and validation. Please flag any overlapping work
    or preferred approach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions