Skip to content

fix: make operation report resilient to deleted agents#3341

Open
deacon-mp wants to merge 1 commit intomasterfrom
fix/operation-deleted-agent-resilience
Open

fix: make operation report resilient to deleted agents#3341
deacon-mp wants to merge 1 commit intomasterfrom
fix/operation-deleted-agent-resilience

Conversation

@deacon-mp
Copy link
Contributor

Summary

  • Fix IndexError in wait_for_links_completion() when an agent referenced by a link has been deleted from the operation
  • Fix report() to build host_group and agents_steps from both self.agents AND unique paws in self.chain, so deleted agents still appear in reports
  • Fix get_skipped_abilities_by_agent() to skip agents missing from the abilities map

Details

When an agent participates in an operation and is later deleted, viewing that operation in the UI would crash because:

  1. wait_for_links_completion used [0] indexing on a filtered list that could be empty
  2. report() built host_group and agents_steps only from self.agents, missing chain links from deleted agents
  3. get_skipped_abilities_by_agent assumed all agents would be in the abilities map

The fix uses self.chain link paws as source of truth alongside self.agents, with safe fallbacks for missing agent objects.

Test plan

  • test_report_with_empty_agents_and_chain_links - verifies report works when all agents deleted but chain has links
  • test_report_with_partial_deleted_agents - verifies report includes steps from both present and deleted agents
  • Existing test_report_includes_steps_for_agents_not_in_host_group continues to pass

Operations that reference agents which have been deleted would error
when generating reports or viewing in the UI. Now falls back to using
link.paw from the chain when agent objects are missing.
@deacon-mp deacon-mp requested a review from Copilot March 16, 2026 14:04
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Improves Operation reporting and link-wait logic so the UI/report generation doesn’t crash when links reference agents that were removed mid-operation.

Changes:

  • Avoids IndexError in wait_for_links_completion() when an agent referenced by a link no longer exists in self.agents.
  • Makes report() include paws seen in self.chain (in addition to self.agents) so deleted agents still show up in the report.
  • Makes get_skipped_abilities_by_agent() tolerate missing ability entries for an agent.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.

File Description
tests/objects/test_operation.py Adds regression tests ensuring report() works with deleted agents (empty/partial agent list).
app/objects/c_operation.py Hardens link-wait and reporting codepaths against deleted agents and missing ability mappings.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 258 to +263
link = [link for link in self.chain if link.id == link_id][0]
if link.can_ignore():
self.add_ignored_link(link.id)
member = [member for member in self.agents if member.paw == link.paw][0]
members = [member for member in self.agents if member.paw == link.paw]
if not members:
continue
Comment on lines 258 to +261
link = [link for link in self.chain if link.id == link_id][0]
if link.can_ignore():
self.add_ignored_link(link.id)
member = [member for member in self.agents if member.paw == link.paw][0]
members = [member for member in self.agents if member.paw == link.paw]
Comment on lines +319 to +323
agent_paw_map = {a.paw: a for a in self.agents}
chain_paws = {link.paw for link in self.chain}
all_paws = set(agent_paw_map.keys()) | chain_paws
host_group = [agent_paw_map[p].display if p in agent_paw_map else dict(paw=p)
for p in all_paws]
abilities_by_agent = await self._get_all_possible_abilities_by_agent(data_svc)
skipped_abilities = []
for agent in self.agents:
if agent.paw not in abilities_by_agent:
for p in all_paws]
report = dict(name=self.name, host_group=host_group,
start=self.start.strftime(self.TIME_FORMAT),
steps=[], finish=self.finish, planner=self.planner.name, adversary=self.adversary.display,
if isinstance(entry, dict) and 'paw' in entry:
paws_in_host_group.append(entry['paw'])
elif hasattr(entry, 'get') and entry.get('paw'):
paws_in_host_group.append(entry['paw'])
@sonarqubecloud
Copy link

@sonarqubecloud
Copy link

❌ The last analysis has failed.

See analysis details on SonarQube Cloud

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants