【Bug已解决】Codex App entered an infinite auto-compaction loop and consumed ~30% of my usage limit 解决方案
·
【Bug已解决】Codex App entered an infinite auto-compaction loop and consumed ~30% of my usage limit 解决方案
原始报错线索:Codex App entered an infinite auto-compaction loop and consumed ~30% of my usage limit(应用陷入「无限自动压缩」循环,反复触发压缩,短时间内耗掉了约 30% 的用量额度)。
一、现象长什么样
应用有个「自动压缩」功能,本意是「上下文快满时压缩一次」。结果:
- 压缩被反复触发,几秒内十几二十次;
- 每次压缩都消耗用量额度(调用模型做摘要);
- 短时间耗尽约 30% 月度额度;
- 手动停止前根本停不下来;
- 查日志发现:压缩后「是否还需要压缩」的判断仍为 True,于是又压,永动机。 根因是压缩的「终止条件」在压缩后依然成立(甚至被压缩本身破坏),形成正反馈死循环,且每次循环都计费。
二、背景:自动化循环的「收敛性」
任何自动触发操作都必须满足:执行后,触发条件应趋向「不再满足」。否则就是失控循环。常见触发条件:
- 上下文长度 > 阈值 → 压缩;
- 文件未保存 → 保存;
- 未同步 → 同步。 若操作本身不改变或不充分改变触发条件,循环就停不下来。更糟的是,若每次操作都计费/耗资源,就会变成「烧钱永动机」。
三、为什么无限循环:根因
3.1 终止条件被操作本身破坏
压缩后上下文没真正变短(可能摘要比原文还长,或又立刻追加新内容),长度仍超阈值 → 再压。
3.2 缺少循环次数 / 频率上限
没有「单位时间最多压 N 次」「连续压 M 次就停」的护栏。
3.3 没有额度熔断
循环消耗额度时,没有「额度消耗过快就暂停」的熔断机制。
3.4 压缩失败仍计入触发
压缩抛错但没标记「已尝试」,下次又触发,错误循环也烧额度。
四、最小可运行复现(终止条件永不假)
下面演示「操作后条件仍为真」如何成死循环:
def needs_compact_bad(ctx_len, threshold):
return ctx_len > threshold
def compact_bad(ctx_len):
# 错误:压缩后长度没降(摘要反而更啰嗦或立即又被填回)
return ctx_len + 10 # 越压越长
if __name__ == "__main__":
length = 1000
threshold = 800
count = 0
while needs_compact_bad(length, threshold):
length = compact_bad(length) # 越压越长
count += 1
if count > 100:
print("失控循环! 已压", count, "次")
break
print("最终长度:", length)
compact_bad 让长度不降反升,循环永不终止——正是烧额度死循环的核心。
五、解决方案一:确保操作使条件收敛
压缩必须真正缩短上下文,且压缩后标记「已压缩」,避免立即再触发:
def compact(ctx, threshold):
"""返回压缩后的上下文与『是否真缩短了』。"""
new_ctx = summarize(ctx) # 真实摘要应显著变短
shortened = len(new_ctx) < len(ctx)
return new_ctx, shortened
def needs_compact(ctx_len, threshold, recently_compacted):
if recently_compacted:
return False # 刚压过,给冷却期
return ctx_len > threshold
if __name__ == "__main__":
# 正确:压缩后显著变短 + 冷却标记,循环收敛
length = 1000
recently = False
for _ in range(5):
if needs_compact(length, 800, recently):
length = int(length * 0.3) # 真正缩短
recently = True
else:
break
print("收敛后长度:", length, "recently:", recently)
压缩真正缩短 + 冷却标记,循环收敛(解决 3.1)。
六、解决方案二:循环护栏(次数 + 频率上限)
无论条件如何,强制限制「单位时间压缩次数」:
import time
class LoopGuard:
def __init__(self, max_per_window=3, window_sec=60):
self.max_per_window = max_per_window
self.window_sec = window_sec
self.timestamps = []
def allow(self) -> bool:
now = time.time()
# 清掉窗口外的记录
self.timestamps = [t for t in self.timestamps if now - t < self.window_sec]
if len(self.timestamps) >= self.max_per_window:
return False
self.timestamps.append(now)
return True
if __name__ == "__main__":
guard = LoopGuard(max_per_window=3, window_sec=60)
for i in range(10):
if guard.allow():
print(f"第 {i+1} 次压缩执行")
else:
print(f"第 {i+1} 次被护栏拦截(频率超限)")
break
护栏保证:即使条件误判为真,60 秒内也最多压 3 次,杜绝烧额度(解决 3.2)。
七、解决方案三:额度熔断(消耗过快即暂停)
压缩消耗额度,需监控「额度消耗速率」,过快则熔断暂停:
class QuotaCircuitBreaker:
def __init__(self, limit, window_sec=3600, burn_rate_alert=0.1):
self.limit = limit
self.used = 0
self.window_sec = window_sec
self.burn_rate_alert = burn_rate_alert # 1 小时内耗超 10% 即告警
self.tripped = False
def charge(self, amount):
if self.tripped:
raise RuntimeError("熔断已触发,暂停自动压缩")
self.used += amount
if self.used / self.limit > self.burn_rate_alert:
self.tripped = True
print(f"[熔断] 额度消耗过快({self.used/self.limit:.0%}),暂停自动压缩")
if __name__ == "__main__":
cb = QuotaCircuitBreaker(limit=100, window_sec=3600, burn_rate_alert=0.1)
for i in range(20):
try:
cb.charge(3) # 每次压缩扣 3 额度
except RuntimeError as e:
print("停止:", e) # 约 30 额度(30%)时熔断
break
额度消耗达 30% 阈值即熔断,自动压缩暂停,不再烧钱(解决 3.3,呼应第 97/110 篇)。
八、解决方案四:失败标记 + 不重复计费
压缩失败要标记「已尝试」,避免错误循环重复计费;且失败不计为成功消耗:
def try_compact_with_meter(ctx, meter):
try:
new_ctx, ok = compact(ctx, 800)
if not ok:
# 没真正缩短:标记失败,本次不计费,且不立即重试
return ctx, {"charged": False, "reason": "未缩短"}
meter.charge(1) # 仅在真正压缩成功时计费
return new_ctx, {"charged": True}
except Exception:
# 失败不计费、标记已尝试,防错误循环烧额度
return ctx, {"charged": False, "reason": "异常"}
if __name__ == "__main__":
meter = QuotaCircuitBreaker(limit=100)
ctx = "x" * 1000
for _ in range(5):
ctx, info = try_compact_with_meter(ctx, meter)
print(info)
失败不重复计费 + 成功才扣额度,错误循环也不再烧钱。
九、排查清单
「自动压缩无限循环烧额度」,按下面排查:
- 压缩是否真收敛?压缩后长度是否真正下降(第五节);
- 有无冷却/连续次数上限(第六节护栏);
- 额度有无熔断?消耗过快是否暂停(第七节,呼应第97/110篇);
- 频率护栏?单位时间最多几次(第六节);
- 失败是否计费?错误循环是否重复扣额度;
- 日志是否记录每次压缩的『压缩前后长度 + 扣费』;
- 是否有手动急停?用户能否立即中断循环;
- 终止条件是否被操作破坏(第五节)。
十、小结
「应用进入无限自动压缩循环耗尽额度」的根因是压缩终止条件在压缩后依然成立(甚至被压缩破坏),形成正反馈死循环,且每次循环都计费。通用修复:
- 收敛性:压缩必须真正缩短上下文 + 冷却标记,循环必须收敛(第五节);
- 频率护栏:次数/时间窗口上限,条件误判也拦得住(第六节);
- 额度熔断:消耗达阈值即暂停自动操作,不再烧钱(第七节,呼应第 97/110 篇);
- 失败不计费:仅成功压缩扣额度,错误循环不重复烧。 一句话:任何「自动触发 + 消耗资源」的操作,都必须满足「执行后条件收敛」,并有次数护栏与额度熔断两道保险。把收敛性当成自动化的前提、把熔断当成计费的底线,自动压缩就再也不会变成「30% 额度蒸发机」——这与第 97 篇配额重置、第 110 篇幂等记账、第 120 篇配额恢复,共同体现「自动操作必须可控、可熔断、可计量」。

更多推荐


所有评论(0)