fixfleet 0.4.1__tar.gz → 0.4.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. {fixfleet-0.4.1/fixfleet.egg-info → fixfleet-0.4.2}/PKG-INFO +12 -1
  2. {fixfleet-0.4.1 → fixfleet-0.4.2}/README.md +11 -0
  3. {fixfleet-0.4.1 → fixfleet-0.4.2}/bugfixer/__init__.py +1 -1
  4. {fixfleet-0.4.1 → fixfleet-0.4.2}/bugfixer/cli.py +15 -1
  5. {fixfleet-0.4.1 → fixfleet-0.4.2}/bugfixer/config.py +3 -0
  6. {fixfleet-0.4.1 → fixfleet-0.4.2}/bugfixer/json_api.py +28 -1
  7. fixfleet-0.4.2/bugfixer/telemetry.py +152 -0
  8. {fixfleet-0.4.1 → fixfleet-0.4.2/fixfleet.egg-info}/PKG-INFO +12 -1
  9. {fixfleet-0.4.1 → fixfleet-0.4.2}/fixfleet.egg-info/SOURCES.txt +1 -0
  10. {fixfleet-0.4.1 → fixfleet-0.4.2}/pyproject.toml +1 -1
  11. {fixfleet-0.4.1 → fixfleet-0.4.2}/tests/test_all.py +3 -0
  12. {fixfleet-0.4.1 → fixfleet-0.4.2}/tests/test_providers_qa.py +5 -0
  13. {fixfleet-0.4.1 → fixfleet-0.4.2}/LICENSE +0 -0
  14. {fixfleet-0.4.1 → fixfleet-0.4.2}/bugfixer/backends/__init__.py +0 -0
  15. {fixfleet-0.4.1 → fixfleet-0.4.2}/bugfixer/backends/_subprocess.py +0 -0
  16. {fixfleet-0.4.1 → fixfleet-0.4.2}/bugfixer/backends/api/__init__.py +0 -0
  17. {fixfleet-0.4.1 → fixfleet-0.4.2}/bugfixer/backends/api/openai_compat.py +0 -0
  18. {fixfleet-0.4.1 → fixfleet-0.4.2}/bugfixer/backends/base.py +0 -0
  19. {fixfleet-0.4.1 → fixfleet-0.4.2}/bugfixer/backends/cli/__init__.py +0 -0
  20. {fixfleet-0.4.1 → fixfleet-0.4.2}/bugfixer/backends/cli/aider.py +0 -0
  21. {fixfleet-0.4.1 → fixfleet-0.4.2}/bugfixer/backends/cli/claude.py +0 -0
  22. {fixfleet-0.4.1 → fixfleet-0.4.2}/bugfixer/backends/cli/codex.py +0 -0
  23. {fixfleet-0.4.1 → fixfleet-0.4.2}/bugfixer/backends/cli/cursor.py +0 -0
  24. {fixfleet-0.4.1 → fixfleet-0.4.2}/bugfixer/backends/cli/gemini.py +0 -0
  25. {fixfleet-0.4.1 → fixfleet-0.4.2}/bugfixer/backends/cli/qwen.py +0 -0
  26. {fixfleet-0.4.1 → fixfleet-0.4.2}/bugfixer/backends/registry.py +0 -0
  27. {fixfleet-0.4.1 → fixfleet-0.4.2}/bugfixer/budget.py +0 -0
  28. {fixfleet-0.4.1 → fixfleet-0.4.2}/bugfixer/confidence.py +0 -0
  29. {fixfleet-0.4.1 → fixfleet-0.4.2}/bugfixer/gitlab.py +0 -0
  30. {fixfleet-0.4.1 → fixfleet-0.4.2}/bugfixer/locator.py +0 -0
  31. {fixfleet-0.4.1 → fixfleet-0.4.2}/bugfixer/parser.py +0 -0
  32. {fixfleet-0.4.1 → fixfleet-0.4.2}/bugfixer/prompt.py +0 -0
  33. {fixfleet-0.4.1 → fixfleet-0.4.2}/bugfixer/providers/__init__.py +0 -0
  34. {fixfleet-0.4.1 → fixfleet-0.4.2}/bugfixer/providers/azure.py +0 -0
  35. {fixfleet-0.4.1 → fixfleet-0.4.2}/bugfixer/providers/base.py +0 -0
  36. {fixfleet-0.4.1 → fixfleet-0.4.2}/bugfixer/providers/bitbucket.py +0 -0
  37. {fixfleet-0.4.1 → fixfleet-0.4.2}/bugfixer/providers/github.py +0 -0
  38. {fixfleet-0.4.1 → fixfleet-0.4.2}/bugfixer/providers/gitlab.py +0 -0
  39. {fixfleet-0.4.1 → fixfleet-0.4.2}/bugfixer/providers/jira.py +0 -0
  40. {fixfleet-0.4.1 → fixfleet-0.4.2}/bugfixer/providers/linear.py +0 -0
  41. {fixfleet-0.4.1 → fixfleet-0.4.2}/bugfixer/providers/registry.py +0 -0
  42. {fixfleet-0.4.1 → fixfleet-0.4.2}/bugfixer/state.py +0 -0
  43. {fixfleet-0.4.1 → fixfleet-0.4.2}/bugfixer/ui.py +0 -0
  44. {fixfleet-0.4.1 → fixfleet-0.4.2}/fixfleet.egg-info/dependency_links.txt +0 -0
  45. {fixfleet-0.4.1 → fixfleet-0.4.2}/fixfleet.egg-info/entry_points.txt +0 -0
  46. {fixfleet-0.4.1 → fixfleet-0.4.2}/fixfleet.egg-info/top_level.txt +0 -0
  47. {fixfleet-0.4.1 → fixfleet-0.4.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fixfleet
3
- Version: 0.4.1
3
+ Version: 0.4.2
4
4
  Summary: Auto-fix bugs from GitHub, GitLab, Jira, Linear, Bitbucket, or Azure DevOps with AI agents — bring your own CLI or free API.
5
5
  Author: Yash Koladiya
6
6
  License-Expression: GPL-3.0-or-later
@@ -405,6 +405,17 @@ No. FixFleet runs **100% locally** on your machine. No FixFleet servers, no Azur
405
405
 
406
406
  </details>
407
407
 
408
+ <details>
409
+ <summary><b>Does FixFleet collect any data?</b></summary>
410
+
411
+ Only anonymous usage analytics (via Google Analytics): event names and coarse metadata like provider key, backend name, and success/failure — **never your code, tokens, project URLs, or issue content**. Used purely to find bugs and see which trackers/backends need attention.
412
+
413
+ Opt out anytime:
414
+ - CLI: `fixfleet --config-set telemetry.enabled=false` (or set `DO_NOT_TRACK=1`)
415
+ - VSCode: uncheck `fixfleet.telemetry` in settings (the global VS Code telemetry setting is respected too)
416
+
417
+ </details>
418
+
408
419
  <details>
409
420
  <summary><b>What's the difference between the VSCode extension and the CLI?</b></summary>
410
421
 
@@ -377,6 +377,17 @@ No. FixFleet runs **100% locally** on your machine. No FixFleet servers, no Azur
377
377
 
378
378
  </details>
379
379
 
380
+ <details>
381
+ <summary><b>Does FixFleet collect any data?</b></summary>
382
+
383
+ Only anonymous usage analytics (via Google Analytics): event names and coarse metadata like provider key, backend name, and success/failure — **never your code, tokens, project URLs, or issue content**. Used purely to find bugs and see which trackers/backends need attention.
384
+
385
+ Opt out anytime:
386
+ - CLI: `fixfleet --config-set telemetry.enabled=false` (or set `DO_NOT_TRACK=1`)
387
+ - VSCode: uncheck `fixfleet.telemetry` in settings (the global VS Code telemetry setting is respected too)
388
+
389
+ </details>
390
+
380
391
  <details>
381
392
  <summary><b>What's the difference between the VSCode extension and the CLI?</b></summary>
382
393
 
@@ -1,6 +1,6 @@
1
1
  """FixFleet — Fleet of AI agents fixing bugs across any issue tracker."""
2
2
 
3
- __version__ = "0.4.1"
3
+ __version__ = "0.4.2"
4
4
  __name_pretty__ = "FixFleet"
5
5
  __tagline__ = "Fleet of AI agents fixing bugs across any tracker"
6
6
  __author__ = "Yash Koladiya"
@@ -5,7 +5,7 @@ import sys
5
5
  from datetime import datetime
6
6
  from pathlib import Path
7
7
 
8
- from . import budget, config, state, ui
8
+ from . import budget, config, state, telemetry, ui
9
9
  from .backends.base import Backend, RunResult
10
10
  from .backends.registry import (
11
11
  API_PRESETS,
@@ -569,6 +569,7 @@ def _fix_issues(selected: list, project_dir: str, project_id: str,
569
569
  failed += 1
570
570
  continue
571
571
 
572
+ telemetry.track("fix_started", {"provider": "gitlab", "backend": backend.name})
572
573
  result: RunResult = backend.run(prompt, project_dir)
573
574
  consumed = check.estimated # use estimate as proxy
574
575
 
@@ -590,6 +591,15 @@ def _fix_issues(selected: list, project_dir: str, project_id: str,
590
591
  if conf.final_score < 0.20 and not conf.files_changed:
591
592
  success = False
592
593
 
594
+ telemetry.track("fix_completed", {
595
+ "provider": "gitlab",
596
+ "backend": backend.name,
597
+ "success": success,
598
+ "timed_out": result.timed_out,
599
+ "confidence": conf.label(),
600
+ "files_changed": len(conf.files_changed),
601
+ })
602
+
593
603
  session_tokens += consumed
594
604
  state.record_usage(
595
605
  backend_name=backend.name,
@@ -636,20 +646,24 @@ def main():
636
646
  try:
637
647
  issues = fetch_bug_issues(token, project_id, date_filter, host=host)
638
648
  except GitLabAuthError as e:
649
+ telemetry.track("provider_error", {"provider": "gitlab", "code": e.code})
639
650
  ui.print_error(e.message)
640
651
  ui.print_info("Open https://gitlab.com/-/user_settings/personal_access_tokens to create a new token.")
641
652
  ui.print_end()
642
653
  sys.exit(1)
643
654
  except GitLabNotFoundError as e:
655
+ telemetry.track("provider_error", {"provider": "gitlab", "code": e.code})
644
656
  ui.print_error(e.message)
645
657
  ui.print_end()
646
658
  sys.exit(1)
647
659
  except GitLabNetworkError as e:
660
+ telemetry.track("provider_error", {"provider": "gitlab", "code": e.code})
648
661
  ui.print_error(e.message)
649
662
  ui.print_info("Check your internet connection and try again.")
650
663
  ui.print_end()
651
664
  sys.exit(1)
652
665
  except GitLabError as e:
666
+ telemetry.track("provider_error", {"provider": "gitlab", "code": e.code})
653
667
  ui.print_error(e.message)
654
668
  ui.print_end()
655
669
  sys.exit(1)
@@ -26,6 +26,9 @@ DEFAULT_CONFIG = {
26
26
  "inline_max_lines": 250,
27
27
  },
28
28
  "skip_already_fixed": True,
29
+ "telemetry": {
30
+ "enabled": True, # anonymous usage events only; see bugfixer/telemetry.py
31
+ },
29
32
  }
30
33
 
31
34
 
@@ -11,7 +11,7 @@ import json
11
11
  import sys
12
12
  from pathlib import Path
13
13
 
14
- from . import __version__, budget, config, state
14
+ from . import __version__, budget, config, state, telemetry
15
15
  from .backends.base import RunResult
16
16
  from .backends.registry import (
17
17
  API_PRESETS,
@@ -139,12 +139,21 @@ def cmd_list_bugs_json(args):
139
139
  date_to=getattr(args, "date_to", None),
140
140
  )
141
141
  except ProviderNotImplementedError as e:
142
+ telemetry.track("provider_error", {"provider": provider.key, "code": e.code})
142
143
  _err_from_gitlab(e)
143
144
  except ProviderError as e:
145
+ telemetry.track("provider_error", {"provider": provider.key, "code": e.code})
144
146
  _err_from_gitlab(e)
145
147
  except Exception as e:
148
+ telemetry.track("unexpected_error", {"where": "list_bugs", "type": type(e).__name__})
146
149
  _err(f"unexpected error: {e}", error_code="unexpected")
147
150
 
151
+ telemetry.track("bugs_listed", {
152
+ "provider": provider.key,
153
+ "count": len(issues),
154
+ "date_filter": bool(args.date or getattr(args, "date_from", None) or getattr(args, "date_to", None)),
155
+ })
156
+
148
157
  payload_issues = []
149
158
  for i in issues:
150
159
  parsed = parse_issue(i)
@@ -238,8 +247,10 @@ def cmd_fix_issue(args):
238
247
  try:
239
248
  issues = provider.fetch_bugs(token=token, project_id=project_id, host=host)
240
249
  except ProviderNotImplementedError as e:
250
+ telemetry.track("provider_error", {"provider": provider.key, "code": e.code})
241
251
  _err_from_gitlab(e)
242
252
  except ProviderError as e:
253
+ telemetry.track("provider_error", {"provider": provider.key, "code": e.code})
243
254
  _err_from_gitlab(e)
244
255
  target = next(
245
256
  (i for i in issues if str(i.get("iid")) == str(issue_iid)),
@@ -261,6 +272,7 @@ def cmd_fix_issue(args):
261
272
  budgets=cfg_budgets,
262
273
  )
263
274
  if not check.allowed and not getattr(args, "force", False):
275
+ telemetry.track("budget_blocked", {"backend": backend.name})
264
276
  _emit({
265
277
  "ok": False,
266
278
  "code": "budget_exceeded",
@@ -275,6 +287,7 @@ def cmd_fix_issue(args):
275
287
  sys.exit(1)
276
288
 
277
289
  # Run backend
290
+ telemetry.track("fix_started", {"provider": provider.key, "backend": backend.name})
278
291
  result: RunResult = backend.run(prompt, project_dir)
279
292
  conf = evaluate_confidence(
280
293
  result.stdout, project_dir,
@@ -283,6 +296,14 @@ def cmd_fix_issue(args):
283
296
  )
284
297
 
285
298
  success = result.ok and (conf.final_score >= 0.20 or conf.files_changed)
299
+ telemetry.track("fix_completed", {
300
+ "provider": provider.key,
301
+ "backend": backend.name,
302
+ "success": success,
303
+ "timed_out": result.timed_out,
304
+ "confidence": conf.label(),
305
+ "files_changed": len(conf.files_changed),
306
+ })
286
307
  state.record_usage(
287
308
  backend_name=backend.name,
288
309
  tokens=check.estimated,
@@ -422,25 +443,31 @@ def main():
422
443
  args.backend = args.backend or os.environ.get("BUGFIXER_BACKEND", "")
423
444
 
424
445
  if args.backends_json:
446
+ telemetry.track("cli_start", {"mode": "backends_json"})
425
447
  cmd_backends_json()
426
448
  return
427
449
  if args.providers_json:
428
450
  cmd_providers_json()
429
451
  return
430
452
  if args.list_bugs_json:
453
+ telemetry.track("cli_start", {"mode": "list_bugs_json"})
431
454
  cmd_list_bugs_json(args)
432
455
  return
433
456
  if args.fix_issue is not None:
457
+ telemetry.track("cli_start", {"mode": "fix_issue"})
434
458
  cmd_fix_issue(args)
435
459
  return
436
460
  if args.config_get:
437
461
  cmd_config_get()
438
462
  return
439
463
  if args.config_set:
464
+ telemetry.track("config_set", {"keys": ",".join(p.split("=")[0] for p in args.config_set)})
440
465
  cmd_config_set(args)
441
466
  return
442
467
 
443
468
  # No JSON flag → fall through to interactive CLI
469
+ telemetry.maybe_show_notice()
470
+ telemetry.track("cli_start", {"mode": "interactive"})
444
471
  from .cli import main as interactive_main
445
472
  interactive_main()
446
473
 
@@ -0,0 +1,152 @@
1
+ """Anonymous usage analytics via GA4 Measurement Protocol (Firebase Analytics).
2
+
3
+ Privacy rules (do not weaken):
4
+ - Sends ONLY event names + coarse metadata (provider key, backend name,
5
+ success bool, error codes, version). NEVER tokens, URLs, project names,
6
+ issue titles, file paths, or code.
7
+ - Anonymous random client id stored locally in ~/.bugfixer-analytics.json.
8
+ - Opt-out: `fixfleet --config-set telemetry.enabled=false`, or env
9
+ FIXFLEET_TELEMETRY=0 / DO_NOT_TRACK=1.
10
+ - Fire-and-forget on a daemon thread with a short timeout; a telemetry
11
+ failure must never break or slow the tool.
12
+
13
+ Setup: create a Firebase project with Analytics enabled, add a Web app,
14
+ then in GA4 Admin → Data Streams → Measurement Protocol API secrets create
15
+ a secret, and fill the two constants below before release.
16
+ """
17
+
18
+ import json
19
+ import os
20
+ import threading
21
+ import urllib.request
22
+ import uuid
23
+ from pathlib import Path
24
+
25
+ from . import __version__
26
+
27
+ # Fill these from your Firebase/GA4 project (see module docstring).
28
+ # Not filled = telemetry silently disabled.
29
+ MEASUREMENT_ID = "G-RB1CHG2YLD"
30
+ API_SECRET = "DORXWPkpQ2uDnsz0PzzF8A"
31
+
32
+ ENDPOINT = "https://www.google-analytics.com/mp/collect"
33
+ DEBUG_ENDPOINT = "https://www.google-analytics.com/debug/mp/collect"
34
+
35
+ _ID_PATH = Path.home() / ".bugfixer-analytics.json"
36
+
37
+ _NOTICE = (
38
+ "FixFleet collects anonymous usage events (event names + coarse metadata "
39
+ "only — never your code, tokens, URLs, or issue content) to find bugs and "
40
+ "improve the tool. Disable anytime:\n"
41
+ " fixfleet --config-set telemetry.enabled=false\n"
42
+ "This notice is shown once."
43
+ )
44
+
45
+
46
+ def _load_id_state() -> dict:
47
+ try:
48
+ return json.loads(_ID_PATH.read_text())
49
+ except (OSError, json.JSONDecodeError):
50
+ return {}
51
+
52
+
53
+ def _save_id_state(state: dict):
54
+ try:
55
+ _ID_PATH.write_text(json.dumps(state))
56
+ except OSError:
57
+ pass
58
+
59
+
60
+ def client_id() -> str:
61
+ state = _load_id_state()
62
+ cid = state.get("client_id")
63
+ if not cid:
64
+ cid = str(uuid.uuid4())
65
+ state["client_id"] = cid
66
+ _save_id_state(state)
67
+ return cid
68
+
69
+
70
+ def enabled() -> bool:
71
+ if not MEASUREMENT_ID or not API_SECRET:
72
+ return False
73
+ if os.environ.get("DO_NOT_TRACK", "") not in ("", "0"):
74
+ return False
75
+ if os.environ.get("FIXFLEET_TELEMETRY", "") in ("0", "false", "off"):
76
+ return False
77
+ try:
78
+ from . import config
79
+ t = config.load().get("telemetry")
80
+ if isinstance(t, dict) and t.get("enabled") is False:
81
+ return False
82
+ except Exception:
83
+ pass
84
+ return True
85
+
86
+
87
+ def maybe_show_notice():
88
+ """Print the one-time telemetry disclosure to stderr (interactive CLI only)."""
89
+ if not enabled():
90
+ return
91
+ state = _load_id_state()
92
+ if state.get("notified"):
93
+ return
94
+ state["notified"] = True
95
+ _save_id_state(state)
96
+ import sys
97
+ print(f"\n{_NOTICE}\n", file=sys.stderr)
98
+
99
+
100
+ def _sanitize(params: dict) -> dict:
101
+ """Keep only short scalar values — defense against accidental PII."""
102
+ out = {}
103
+ for k, v in (params or {}).items():
104
+ if isinstance(v, bool):
105
+ out[k] = "true" if v else "false"
106
+ elif isinstance(v, (int, float)):
107
+ out[k] = v
108
+ elif isinstance(v, str):
109
+ out[k] = v[:80]
110
+ return out
111
+
112
+
113
+ def track(event_name: str, params: dict = None):
114
+ """Send one GA4 event. Non-blocking, never raises."""
115
+ if not enabled():
116
+ return
117
+
118
+ body = {
119
+ "client_id": client_id(),
120
+ "events": [{
121
+ "name": event_name,
122
+ "params": {
123
+ "app_version": __version__,
124
+ "platform": os.name,
125
+ "engagement_time_msec": 1,
126
+ **_sanitize(params),
127
+ },
128
+ }],
129
+ }
130
+ debug = os.environ.get("FIXFLEET_TELEMETRY_DEBUG", "") == "1"
131
+ if debug:
132
+ body["events"][0]["params"]["debug_mode"] = 1
133
+ endpoint = DEBUG_ENDPOINT if debug else ENDPOINT
134
+ url = f"{endpoint}?measurement_id={MEASUREMENT_ID}&api_secret={API_SECRET}"
135
+
136
+ def _send():
137
+ try:
138
+ req = urllib.request.Request(
139
+ url,
140
+ data=json.dumps(body).encode(),
141
+ headers={"Content-Type": "application/json"},
142
+ method="POST",
143
+ )
144
+ with urllib.request.urlopen(req, timeout=3) as resp:
145
+ if debug:
146
+ import sys
147
+ print(f"[telemetry] {event_name}: {resp.read().decode()[:300]}",
148
+ file=sys.stderr)
149
+ except Exception:
150
+ pass # telemetry must never break the tool
151
+
152
+ threading.Thread(target=_send, daemon=True).start()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fixfleet
3
- Version: 0.4.1
3
+ Version: 0.4.2
4
4
  Summary: Auto-fix bugs from GitHub, GitLab, Jira, Linear, Bitbucket, or Azure DevOps with AI agents — bring your own CLI or free API.
5
5
  Author: Yash Koladiya
6
6
  License-Expression: GPL-3.0-or-later
@@ -405,6 +405,17 @@ No. FixFleet runs **100% locally** on your machine. No FixFleet servers, no Azur
405
405
 
406
406
  </details>
407
407
 
408
+ <details>
409
+ <summary><b>Does FixFleet collect any data?</b></summary>
410
+
411
+ Only anonymous usage analytics (via Google Analytics): event names and coarse metadata like provider key, backend name, and success/failure — **never your code, tokens, project URLs, or issue content**. Used purely to find bugs and see which trackers/backends need attention.
412
+
413
+ Opt out anytime:
414
+ - CLI: `fixfleet --config-set telemetry.enabled=false` (or set `DO_NOT_TRACK=1`)
415
+ - VSCode: uncheck `fixfleet.telemetry` in settings (the global VS Code telemetry setting is respected too)
416
+
417
+ </details>
418
+
408
419
  <details>
409
420
  <summary><b>What's the difference between the VSCode extension and the CLI?</b></summary>
410
421
 
@@ -12,6 +12,7 @@ bugfixer/locator.py
12
12
  bugfixer/parser.py
13
13
  bugfixer/prompt.py
14
14
  bugfixer/state.py
15
+ bugfixer/telemetry.py
15
16
  bugfixer/ui.py
16
17
  bugfixer/backends/__init__.py
17
18
  bugfixer/backends/_subprocess.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "fixfleet"
7
- version = "0.4.1"
7
+ version = "0.4.2"
8
8
  description = "Auto-fix bugs from GitHub, GitLab, Jira, Linear, Bitbucket, or Azure DevOps with AI agents — bring your own CLI or free API."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -11,6 +11,9 @@ from pathlib import Path
11
11
  # Make sure the package is importable
12
12
  sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
13
13
 
14
+ # Telemetry must never fire during tests.
15
+ os.environ["FIXFLEET_TELEMETRY"] = "0"
16
+
14
17
  from bugfixer import budget, confidence
15
18
  from bugfixer.backends import registry
16
19
  from bugfixer.cli import _sanitize_path
@@ -9,12 +9,17 @@ provider-side anyway.
9
9
 
10
10
  import io
11
11
  import json
12
+ import os
12
13
  import sys
13
14
  import unittest
14
15
  import urllib.error
15
16
  from pathlib import Path
16
17
  from unittest.mock import patch, MagicMock
17
18
 
19
+ # Telemetry must never fire during tests — its background thread would consume
20
+ # the mocked urlopen side_effect responses and poison unrelated assertions.
21
+ os.environ["FIXFLEET_TELEMETRY"] = "0"
22
+
18
23
  sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
19
24
 
20
25
  from bugfixer.providers import (
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes