Skip to content

Add cooling preset temperatures for HEAT_COOL mode#1945

Open
quittung wants to merge 7 commits intoKartoffelToby:developfrom
quittung:cooling-presets
Open

Add cooling preset temperatures for HEAT_COOL mode#1945
quittung wants to merge 7 commits intoKartoffelToby:developfrom
quittung:cooling-presets

Conversation

@quittung
Copy link

@quittung quittung commented Feb 23, 2026

Summary

  • When a cooler entity is configured, presets now also control the cooling target temperature via new BetterThermostatPresetCoolNumber entities
  • Heating-only thermostats are unaffected — no extra entities, no UI clutter
  • Preset switching saves and restores both heating and cooling targets
  • Naming adapts to configuration: heating-only shows "Away", with cooler shows "Away Min" / "Away Max"

Details

Currently, switching presets only changes bt_target_temp (the heating floor). When a cooler entity is configured and the thermostat operates in HEAT_COOL mode, the cooling ceiling (bt_target_cooltemp) is left unchanged. This means users have to manually adjust cooling targets after every preset switch.

Changes:

climate.py:

  • Added _preset_cool_temperatures dict with sensible defaults (Away: 28, Home: 24, Sleep: 22, Comfort: 24, Eco: 27, Boost: 20, Activity: 23)
  • async_set_preset_mode now saves/restores bt_target_cooltemp when entering/leaving presets (only when cooler_entity_id is set)
  • Cooling preset temps exposed in extra_state_attributes for persistence
  • Startup restore applies cooling preset temp alongside heating

number.py:

  • Heating preset numbers are only created when heater TRVs exist
  • Cooling preset numbers (BetterThermostatPresetCoolNumber) are only created when a cooler is configured
  • Heating numbers get a "Min" suffix and cooling numbers get a "Max" suffix when both exist, keeping names short for the UI

Naming rationale: The existing "Preset Away" naming was already getting clipped in the device info UI. Adding "Cool" to distinguish cooling entities made it worse — "Preset Away" and "Preset Away..." were indistinguishable. Dropping the "Preset" prefix and using short Min/Max suffixes keeps names readable. For heating-only setups (no cooler), the suffix is omitted entirely since there's no ambiguity.

Defaults

Preset Heating (Min) Cooling (Max)
None 20 24
Away 16 28
Boost 24 20
Comfort 21 24
Eco 19 27
Home 20 24
Sleep 18 22
Activity 22 23

Test plan

  • Thermostat with cooler: verify both Min and Max number entities appear per preset
  • Thermostat without cooler: verify only heating numbers appear (no suffix)
  • Switch presets on HEAT_COOL thermostat: verify both bt_target_temp and bt_target_cooltemp change
  • Switch to preset, then back to None: verify both temps restore to previous values
  • Change cooling preset number while preset is active: verify bt_target_cooltemp updates immediately
  • Restart HA: verify cooling preset temps are restored via RestoreEntity

Summary by CodeRabbit

Release Notes

  • New Features
    • Added cooling preset support enabling separate temperature targets for each preset mode
    • New controls for managing cooling temperatures alongside heating presets
    • Enhanced preset switching to properly apply and restore both heating and cooling targets
When a cooler entity is configured, presets now also control the cooling
target temperature. Adds BetterThermostatPresetCoolNumber entities
alongside the existing heating preset numbers.

- Heating-only thermostats: unchanged (e.g. "Away")
- Thermostats with cooler: "Away Min" / "Away Max" number entities
- Preset switching saves/restores both heating and cooling targets
- Cooling preset temps persisted via state attributes and RestoreEntity
- Sensible defaults: Away 28, Home 24, Sleep 22, Comfort 24, Eco 27,
  Boost 20, Activity 23

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Feb 23, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)

✅ Unit Test PR creation complete.

  • Create PR with unit tests
  • Commit unit tests in branch cooling-presets
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

KartoffelToby and others added 2 commits February 23, 2026 20:53
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
custom_components/better_thermostat/climate.py (2)

3590-3604: ⚠️ Potential issue | 🟡 Minor

_preset_cool_temperature is not persisted or restored across restarts.

The heating counterpart _preset_temperature is persisted via extra_state_attributes (as ATTR_STATE_PRESET_TEMPERATURE) and restored during startup (lines 1271–1282). The new _preset_cool_temperature has no corresponding attribute or restore logic.

If HA restarts while a non-NONE preset is active, the "before-preset" cooling target is lost. When the user later returns to PRESET_NONE, bt_target_cooltemp won't be restored to its pre-preset value.

Proposed fix — add to extra_state_attributes and startup restore

In extra_state_attributes (around line 2675):

             ATTR_STATE_PRESET_TEMPERATURE: self._preset_temperature,
+            "bt_preset_cool_temperature": self._preset_cool_temperature,

In startup restore (after line 1282):

+                if old_state.attributes.get("bt_preset_cool_temperature", None) is not None:
+                    self._preset_cool_temperature = convert_to_float(
+                        str(old_state.attributes.get("bt_preset_cool_temperature", None)),
+                        self.device_name,
+                        "startup()",
+                    )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@custom_components/better_thermostat/climate.py` around lines 3590 - 3604, Add
persistence and restore for the cooling preset temperature: include
_preset_cool_temperature in extra_state_attributes (using a new
ATTR_STATE_PRESET_COOL_TEMPERATURE constant analogous to
ATTR_STATE_PRESET_TEMPERATURE) so its value is saved, and update the
startup/restore logic (where _preset_temperature is restored) to read that
attribute, set self._preset_cool_temperature and, if present, restore
self.bt_target_cooltemp accordingly; ensure references to
_preset_cool_temperature, bt_target_cooltemp, extra_state_attributes and the new
ATTR_STATE_PRESET_COOL_TEMPERATURE are updated consistently.

3623-3642: ⚠️ Potential issue | 🟠 Major

No heating/cooling ordering enforcement after preset application.

async_set_preset_mode directly assigns both bt_target_temp (line 3625) and bt_target_cooltemp (line 3636) without checking that bt_target_cooltemp > bt_target_temp. The async_set_temperature method has explicit ordering enforcement (lines 3248–3263 and others), but async_set_preset_mode bypasses it.

Even if the BOOST defaults are fixed, a user could configure presets via the number entities such that heating ≥ cooling. Add a post-application check:

Proposed fix
                 if self.cooler_entity_id is not None and preset_mode in self._preset_cool_temperatures:
                     cool_temp = self._preset_cool_temperatures[preset_mode]
                     self.bt_target_cooltemp = min(self.max_temp, max(self.min_temp, cool_temp))
                     _LOGGER.debug(
                         "better_thermostat %s: Applied preset %s cooling temperature: %s°C",
                         self.device_name,
                         preset_mode,
                         self.bt_target_cooltemp,
                     )
+
+                # Enforce ordering: cooling target must be above heating target
+                if (
+                    self.cooler_entity_id is not None
+                    and self.bt_target_cooltemp is not None
+                    and self.bt_target_temp is not None
+                    and self.bt_target_cooltemp <= self.bt_target_temp
+                ):
+                    step = self.bt_target_temp_step or 0.5
+                    self.bt_target_cooltemp = self.bt_target_temp + step
+                    _LOGGER.warning(
+                        "better_thermostat %s: Preset %s cooling target adjusted to %.2f to stay above heating target %.2f",
+                        self.device_name,
+                        preset_mode,
+                        self.bt_target_cooltemp,
+                        self.bt_target_temp,
+                    )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@custom_components/better_thermostat/climate.py` around lines 3623 - 3642,
async_set_preset_mode sets self.bt_target_temp and self.bt_target_cooltemp
directly and can violate the required invariant that cooling > heating; mirror
the ordering enforcement used in async_set_temperature: after computing and
assigning bt_target_temp and (if present) bt_target_cooltemp, check if
self.bt_target_cooltemp is not None and self.bt_target_cooltemp <=
self.bt_target_temp, and if so adjust the cooling value to at least
self.bt_target_temp + the minimal step (or smallest allowed delta used by
async_set_temperature), clamped to self.max_temp/self.min_temp, then log the
adjustment; this keeps the same variables (bt_target_temp, bt_target_cooltemp,
self._preset_cool_temperatures) and reuses the ordering logic from
async_set_temperature to enforce heating < cooling.
🧹 Nitpick comments (1)
custom_components/better_thermostat/number.py (1)

204-223: Consider sharing a base class to reduce duplication between preset number entities.

BetterThermostatPresetCoolNumber is nearly identical to BetterThermostatPresetNumber — the same class attributes, similar __init__, async_added_to_hass, device_info, native_value, and async_set_native_value. The only differences are:

  • Which dict is used (_preset_cool_temperatures vs _preset_temperatures)
  • The unique_id suffix (_cool)
  • The name suffix (Max vs Min / none)
  • The async_set_temperature call kwarg (target_temp_high vs temperature)

This is fine for now, but extracting a shared base or parameterizing a single class would reduce maintenance burden if more logic is added later.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@custom_components/better_thermostat/number.py` around lines 204 - 223,
Refactor the nearly identical BetterThermostatPresetCoolNumber and
BetterThermostatPresetNumber by extracting a shared base class (e.g.,
BetterThermostatPresetBaseNumber) or by parameterizing a single class to accept
the differing bits: the preset dict to read/write (_preset_cool_temperatures vs
_preset_temperatures), the unique_id/name suffix (`_cool` and "Max" vs
none/"Min"), and the temperature kwarg used in async_set_temperature
(`target_temp_high` vs `temperature`). Move the common attributes (_attr_*
declarations), __init__ logic for min/max/step, async_added_to_hass,
device_info, native_value, and async_set_native_value into the
base/parameterized class and use constructor params or subclass properties to
supply the three differences so both original classes become thin wrappers that
pass the correct dict, id/name suffix, and async_set_temperature kwarg.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@custom_components/better_thermostat/climate.py`:
- Around line 412-423: The BOOST preset currently in _preset_cool_temperatures
(PRESET_BOOST = 20.0) inverts heat/cool targets for HEAT_COOL mode (heating=24.0
vs cooling=20.0); update the preset defaults so heating < cooling: either change
the PRESET_BOOST value in _preset_cool_temperatures to be >= the BOOST heating
value, swap the BOOST values between the heat and cool preset maps, or remove
BOOST from the cooling presets; also update _original_preset_cool_temperatures
after the change so copies remain consistent and verify behavior when mode ==
HEAT_COOL.

---

Outside diff comments:
In `@custom_components/better_thermostat/climate.py`:
- Around line 3590-3604: Add persistence and restore for the cooling preset
temperature: include _preset_cool_temperature in extra_state_attributes (using a
new ATTR_STATE_PRESET_COOL_TEMPERATURE constant analogous to
ATTR_STATE_PRESET_TEMPERATURE) so its value is saved, and update the
startup/restore logic (where _preset_temperature is restored) to read that
attribute, set self._preset_cool_temperature and, if present, restore
self.bt_target_cooltemp accordingly; ensure references to
_preset_cool_temperature, bt_target_cooltemp, extra_state_attributes and the new
ATTR_STATE_PRESET_COOL_TEMPERATURE are updated consistently.
- Around line 3623-3642: async_set_preset_mode sets self.bt_target_temp and
self.bt_target_cooltemp directly and can violate the required invariant that
cooling > heating; mirror the ordering enforcement used in
async_set_temperature: after computing and assigning bt_target_temp and (if
present) bt_target_cooltemp, check if self.bt_target_cooltemp is not None and
self.bt_target_cooltemp <= self.bt_target_temp, and if so adjust the cooling
value to at least self.bt_target_temp + the minimal step (or smallest allowed
delta used by async_set_temperature), clamped to self.max_temp/self.min_temp,
then log the adjustment; this keeps the same variables (bt_target_temp,
bt_target_cooltemp, self._preset_cool_temperatures) and reuses the ordering
logic from async_set_temperature to enforce heating < cooling.

---

Nitpick comments:
In `@custom_components/better_thermostat/number.py`:
- Around line 204-223: Refactor the nearly identical
BetterThermostatPresetCoolNumber and BetterThermostatPresetNumber by extracting
a shared base class (e.g., BetterThermostatPresetBaseNumber) or by
parameterizing a single class to accept the differing bits: the preset dict to
read/write (_preset_cool_temperatures vs _preset_temperatures), the
unique_id/name suffix (`_cool` and "Max" vs none/"Min"), and the temperature
kwarg used in async_set_temperature (`target_temp_high` vs `temperature`). Move
the common attributes (_attr_* declarations), __init__ logic for min/max/step,
async_added_to_hass, device_info, native_value, and async_set_native_value into
the base/parameterized class and use constructor params or subclass properties
to supply the three differences so both original classes become thin wrappers
that pass the correct dict, id/name suffix, and async_set_temperature kwarg.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 960dfc6 and d46d6d9.

📒 Files selected for processing (2)
  • custom_components/better_thermostat/climate.py
  • custom_components/better_thermostat/number.py
Comment on lines 412 to 423
self._preset_cool_temperatures = {
PRESET_NONE: 24.0,
PRESET_AWAY: 28.0,
PRESET_BOOST: 20.0,
PRESET_COMFORT: 24.0,
PRESET_ECO: 27.0,
PRESET_HOME: 24.0,
PRESET_SLEEP: 22.0,
PRESET_ACTIVITY: 23.0,
}
self._original_preset_cool_temperatures = self._preset_cool_temperatures.copy()
self._preset_cool_temperature = None # saved cool temp before entering preset
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

BOOST preset defaults will invert heating/cooling targets in HEAT_COOL mode.

The BOOST preset has heating=24.0 and cooling=20.0, which means bt_target_temp (24) > bt_target_cooltemp (20). In HEAT_COOL mode, the low target (heating) must be below the high target (cooling), otherwise the system would simultaneously try to heat to 24°C and cool to 20°C.

All other presets are correctly ordered (heating < cooling). BOOST is the only violation.

Proposed fix — swap or adjust BOOST defaults
         self._preset_cool_temperatures = {
             PRESET_NONE: 24.0,
             PRESET_AWAY: 28.0,
-            PRESET_BOOST: 20.0,
+            PRESET_BOOST: 28.0,
             PRESET_COMFORT: 24.0,
             PRESET_ECO: 27.0,
             PRESET_HOME: 24.0,
             PRESET_SLEEP: 22.0,
             PRESET_ACTIVITY: 23.0,
         }

Alternatively, if "BOOST cooling" should mean aggressive cooling (low setpoint), then the heating BOOST should also be lowered below it, or BOOST could be excluded from cooling presets entirely. The choice depends on the intended semantics of BOOST in dual-mode setups.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@custom_components/better_thermostat/climate.py` around lines 412 - 423, The
BOOST preset currently in _preset_cool_temperatures (PRESET_BOOST = 20.0)
inverts heat/cool targets for HEAT_COOL mode (heating=24.0 vs cooling=20.0);
update the preset defaults so heating < cooling: either change the PRESET_BOOST
value in _preset_cool_temperatures to be >= the BOOST heating value, swap the
BOOST values between the heat and cool preset maps, or remove BOOST from the
cooling presets; also update _original_preset_cool_temperatures after the change
so copies remain consistent and verify behavior when mode == HEAT_COOL.
KartoffelToby and others added 3 commits February 23, 2026 21:03
- Fix BOOST cooling default (20 -> 28) to avoid inverting heat/cool targets
- Persist _preset_cool_temperature across restarts via state attributes
- Add heating < cooling ordering enforcement in async_set_preset_mode
- Fix control queue not triggered when cooling preset number changes
  (bypass async_set_temperature, set bt_target_cooltemp directly)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
custom_components/better_thermostat/climate.py (1)

1283-1293: Consider defining constants for the new attribute keys.

The heating preset temperature uses the ATTR_STATE_PRESET_TEMPERATURE constant, but the cooling counterparts use hardcoded strings "bt_preset_cool_temperature" and "bt_preset_cool_temperatures" in three places (lines 1284, 2687, 2702). Extracting these into constants in utils/const.py would keep the pattern consistent and reduce the risk of typo-induced mismatches.

Also applies to: 2687-2687, 2702-2702

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@custom_components/better_thermostat/climate.py` around lines 1283 - 1293,
Extract the hardcoded cooling attribute keys ("bt_preset_cool_temperature" and
"bt_preset_cool_temperatures") into named constants in utils/const.py (e.g.,
ATTR_STATE_PRESET_COOL_TEMPERATURE and ATTR_STATE_PRESET_COOL_TEMPERATURES) and
replace all direct string usages in climate.py—including the startup() logic
that sets self._preset_cool_temperature and the other two occurrences
referenced—to use those constants; update any imports in climate.py to pull the
new constants and ensure the existing heating constant usage
(ATTR_STATE_PRESET_TEMPERATURE) remains consistent with this pattern.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@custom_components/better_thermostat/number.py`:
- Around line 258-272: The async_set_native_value in
BetterThermostatPresetNumber sets _bt_climate.bt_target_cooltemp directly when
the preset is active, which can violate ordering with
_bt_climate.bt_target_temp; modify async_set_native_value to enforce cooling >
heating before assigning (similar to async_set_temperature) — e.g., compute and
clamp the new cool target against _bt_climate.bt_target_temp (or call
_bt_climate.async_set_temperature-like logic) so bt_target_cooltemp is adjusted
if necessary, then proceed to set bt_target_cooltemp, queue control_queue_task
when _bt_climate.bt_hvac_mode != HVACMode.OFF, and call async_write_ha_state on
both entities.

---

Nitpick comments:
In `@custom_components/better_thermostat/climate.py`:
- Around line 1283-1293: Extract the hardcoded cooling attribute keys
("bt_preset_cool_temperature" and "bt_preset_cool_temperatures") into named
constants in utils/const.py (e.g., ATTR_STATE_PRESET_COOL_TEMPERATURE and
ATTR_STATE_PRESET_COOL_TEMPERATURES) and replace all direct string usages in
climate.py—including the startup() logic that sets self._preset_cool_temperature
and the other two occurrences referenced—to use those constants; update any
imports in climate.py to pull the new constants and ensure the existing heating
constant usage (ATTR_STATE_PRESET_TEMPERATURE) remains consistent with this
pattern.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 960dfc6 and b6e5239.

📒 Files selected for processing (2)
  • custom_components/better_thermostat/climate.py
  • custom_components/better_thermostat/number.py
Comment on lines 258 to 272
async def async_set_native_value(self, value: float) -> None:
"""Update the current value."""
self._bt_climate._preset_cool_temperatures[self._preset_mode] = value

# If this preset is currently active, update the cooling target immediately.
# We set bt_target_cooltemp directly and trigger the control queue because
# async_set_temperature does not trigger the control queue when only
# target_temp_high is provided.
if self._bt_climate.preset_mode == self._preset_mode:
self._bt_climate.bt_target_cooltemp = value
if self._bt_climate.bt_hvac_mode != HVACMode.OFF:
await self._bt_climate.control_queue_task.put(self._bt_climate)

self.async_write_ha_state()
self._bt_climate.async_write_ha_state()
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing heating/cooling ordering enforcement when setting cooling preset directly.

When the preset is active, bt_target_cooltemp is set directly (line 267) without verifying it stays above bt_target_temp. The heating counterpart (BetterThermostatPresetNumber) is protected because it routes through async_set_temperature, which enforces ordering. This bypass could leave the system with inverted targets (cooltemp ≤ heat_target) until the next preset switch.

Proposed fix — add ordering enforcement
     async def async_set_native_value(self, value: float) -> None:
         """Update the current value."""
         self._bt_climate._preset_cool_temperatures[self._preset_mode] = value
 
         # If this preset is currently active, update the cooling target immediately.
-        # We set bt_target_cooltemp directly and trigger the control queue because
-        # async_set_temperature does not trigger the control queue when only
-        # target_temp_high is provided.
         if self._bt_climate.preset_mode == self._preset_mode:
             self._bt_climate.bt_target_cooltemp = value
+            # Enforce ordering: cooling target must be above heating target
+            if (
+                self._bt_climate.bt_target_temp is not None
+                and self._bt_climate.bt_target_cooltemp <= self._bt_climate.bt_target_temp
+            ):
+                step = self._bt_climate.bt_target_temp_step or 0.5
+                self._bt_climate.bt_target_cooltemp = self._bt_climate.bt_target_temp + step
             if self._bt_climate.bt_hvac_mode != HVACMode.OFF:
                 await self._bt_climate.control_queue_task.put(self._bt_climate)
 
         self.async_write_ha_state()
         self._bt_climate.async_write_ha_state()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@custom_components/better_thermostat/number.py` around lines 258 - 272, The
async_set_native_value in BetterThermostatPresetNumber sets
_bt_climate.bt_target_cooltemp directly when the preset is active, which can
violate ordering with _bt_climate.bt_target_temp; modify async_set_native_value
to enforce cooling > heating before assigning (similar to async_set_temperature)
— e.g., compute and clamp the new cool target against _bt_climate.bt_target_temp
(or call _bt_climate.async_set_temperature-like logic) so bt_target_cooltemp is
adjusted if necessary, then proceed to set bt_target_cooltemp, queue
control_queue_task when _bt_climate.bt_hvac_mode != HVACMode.OFF, and call
async_write_ha_state on both entities.
- Extract shared _BetterThermostatPresetBaseNumber base class
- Add heating < cooling ordering check in cooling number setter
  to prevent inverted targets when editing cooling preset directly

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Feb 28, 2026

Note

Unit test generation is a public access feature. Expect some limitations and changes as we gather feedback and continue to improve it.


Generating unit tests... This may take up to 20 minutes.

1 similar comment
@coderabbitai
Copy link

coderabbitai bot commented Feb 28, 2026

Note

Unit test generation is a public access feature. Expect some limitations and changes as we gather feedback and continue to improve it.


Generating unit tests... This may take up to 20 minutes.

@coderabbitai
Copy link

coderabbitai bot commented Feb 28, 2026

❌ Failed to create PR with unit tests: AGENT_CHAT: Failed to open pull request

1 similar comment
@coderabbitai
Copy link

coderabbitai bot commented Feb 28, 2026

❌ Failed to create PR with unit tests: AGENT_CHAT: Failed to open pull request

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

Labels

None yet

2 participants