devs-webhook 3.3.6__tar.gz → 3.4.1__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 (53) hide show
  1. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/PKG-INFO +1 -3
  2. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/README.md +0 -2
  3. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/config.py +5 -9
  4. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/core/claude_dispatcher.py +20 -7
  5. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/core/container_pool.py +13 -13
  6. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/main_cli.py +9 -0
  7. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook.egg-info/PKG-INFO +1 -3
  8. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/pyproject.toml +1 -1
  9. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/LICENSE +0 -0
  10. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/__init__.py +0 -0
  11. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/app.py +0 -0
  12. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/cli/__init__.py +0 -0
  13. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/cli/worker.py +0 -0
  14. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/core/__init__.py +0 -0
  15. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/core/base_dispatcher.py +0 -0
  16. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/core/deduplication.py +0 -0
  17. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/core/repository_manager.py +0 -0
  18. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/core/task_processor.py +0 -0
  19. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/core/test_dispatcher.py +0 -0
  20. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/core/webhook_config.py +0 -0
  21. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/core/webhook_handler.py +0 -0
  22. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/github/__init__.py +0 -0
  23. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/github/app_auth.py +0 -0
  24. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/github/client.py +0 -0
  25. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/github/models.py +0 -0
  26. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/github/parser.py +0 -0
  27. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/sources/__init__.py +0 -0
  28. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/sources/base.py +0 -0
  29. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/sources/sqs_source.py +0 -0
  30. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/sources/webhook_source.py +0 -0
  31. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/utils/__init__.py +0 -0
  32. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/utils/async_utils.py +0 -0
  33. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/utils/container_logs.py +0 -0
  34. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/utils/github.py +0 -0
  35. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/utils/logging.py +0 -0
  36. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/utils/s3_artifacts.py +0 -0
  37. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook/utils/serialization.py +0 -0
  38. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook.egg-info/SOURCES.txt +0 -0
  39. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook.egg-info/dependency_links.txt +0 -0
  40. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook.egg-info/entry_points.txt +0 -0
  41. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook.egg-info/requires.txt +0 -0
  42. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/devs_webhook.egg-info/top_level.txt +0 -0
  43. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/setup.cfg +0 -0
  44. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/tests/test_allowlist.py +0 -0
  45. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/tests/test_authentication.py +0 -0
  46. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/tests/test_authorized_users.py +0 -0
  47. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/tests/test_ci_container_pool.py +0 -0
  48. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/tests/test_cleanup_mode.py +0 -0
  49. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/tests/test_container_logs.py +0 -0
  50. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/tests/test_single_queue.py +0 -0
  51. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/tests/test_sqs_burst.py +0 -0
  52. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/tests/test_stop_container_after_task.py +0 -0
  53. {devs_webhook-3.3.6 → devs_webhook-3.4.1}/tests/test_webhook_parser.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devs-webhook
3
- Version: 3.3.6
3
+ Version: 3.4.1
4
4
  Summary: GitHub webhook handler for automated devcontainer operations with Claude Code
5
5
  Author: Dan Lester
6
6
  License-Expression: MIT
@@ -107,7 +107,6 @@ export AUTHORIZED_TRIGGER_USERS="danlester,admin" # Users who can trigger proce
107
107
  # Optional settings (with defaults)
108
108
  export CONTAINER_POOL="eamonn,harry,darren"
109
109
  export CONTAINER_TIMEOUT_MINUTES="30"
110
- export MAX_CONCURRENT_TASKS="3"
111
110
  export WEBHOOK_HOST="0.0.0.0"
112
111
  export WEBHOOK_PORT="8000"
113
112
  ```
@@ -446,7 +445,6 @@ The webhook handler automatically detects and uses these settings when processin
446
445
  | `AUTHORIZED_TRIGGER_USERS` | (empty - allows all) | Comma-separated list of users who can trigger events |
447
446
  | `CONTAINER_POOL` | `eamonn,harry,darren` | Container names |
448
447
  | `CONTAINER_TIMEOUT_MINUTES` | `30` | Container timeout |
449
- | `MAX_CONCURRENT_TASKS` | `3` | Max parallel tasks |
450
448
  | `REPO_CACHE_DIR` | `~/.devs-webhook/repos` | Repository cache |
451
449
  | `WORKSPACE_DIR` | `~/.devs-webhook/workspaces` | Container workspaces |
452
450
  | `WEBHOOK_HOST` | `0.0.0.0` | Server host |
@@ -50,7 +50,6 @@ export AUTHORIZED_TRIGGER_USERS="danlester,admin" # Users who can trigger proce
50
50
  # Optional settings (with defaults)
51
51
  export CONTAINER_POOL="eamonn,harry,darren"
52
52
  export CONTAINER_TIMEOUT_MINUTES="30"
53
- export MAX_CONCURRENT_TASKS="3"
54
53
  export WEBHOOK_HOST="0.0.0.0"
55
54
  export WEBHOOK_PORT="8000"
56
55
  ```
@@ -389,7 +388,6 @@ The webhook handler automatically detects and uses these settings when processin
389
388
  | `AUTHORIZED_TRIGGER_USERS` | (empty - allows all) | Comma-separated list of users who can trigger events |
390
389
  | `CONTAINER_POOL` | `eamonn,harry,darren` | Container names |
391
390
  | `CONTAINER_TIMEOUT_MINUTES` | `30` | Container timeout |
392
- | `MAX_CONCURRENT_TASKS` | `3` | Max parallel tasks |
393
391
  | `REPO_CACHE_DIR` | `~/.devs-webhook/repos` | Repository cache |
394
392
  | `WORKSPACE_DIR` | `~/.devs-webhook/workspaces` | Container workspaces |
395
393
  | `WEBHOOK_HOST` | `0.0.0.0` | Server host |
@@ -90,8 +90,6 @@ class WebhookConfig(BaseSettings, BaseConfig):
90
90
  "This ensures only one running container per dev name at any time, "
91
91
  "reducing RAM usage when multiple repos are in play."
92
92
  )
93
- max_concurrent_tasks: int = Field(default=3, description="Maximum concurrent tasks")
94
-
95
93
  # Repository settings
96
94
  repo_cache_dir: Path = Field(
97
95
  default_factory=lambda: Path.home() / ".devs" / "repocache",
@@ -279,18 +277,16 @@ class WebhookConfig(BaseSettings, BaseConfig):
279
277
  missing.append("github_webhook_secret (GITHUB_WEBHOOK_SECRET) - required for signature verification")
280
278
 
281
279
  # Task source specific validations
282
- if self.task_source == "webhook":
283
- # Require admin password in production mode
284
- if not self.dev_mode and not self.admin_password:
285
- missing.append("admin_password (ADMIN_PASSWORD) - required in production mode")
286
-
287
- elif self.task_source == "sqs":
280
+ if self.task_source == "sqs":
288
281
  # SQS source requires queue URL
289
282
  if not self.aws_sqs_queue_url:
290
283
  missing.append("aws_sqs_queue_url (AWS_SQS_QUEUE_URL) - required for SQS source")
291
284
 
292
- else:
285
+ elif self.task_source != "webhook":
293
286
  missing.append(f"task_source must be 'webhook' or 'sqs', got '{self.task_source}'")
287
+ # admin_password is only enforced when actually starting the FastAPI server
288
+ # (see main_cli.serve). It's not required for SQS-source operation or for
289
+ # read-only commands like `devs-webhook config`.
294
290
 
295
291
  # Raise error if any required settings are missing
296
292
  if missing:
@@ -176,8 +176,25 @@ class ClaudeDispatcher(BaseDispatcher):
176
176
  logger.info("Created new workspace",
177
177
  container=dev_name,
178
178
  workspace_dir=str(workspace_dir))
179
-
180
- # 3. Build Claude prompt
179
+
180
+ # 3. Ensure container is running with environment variables from DEVS.yml.
181
+ # check_rebuild defaults to True so the image is rebuilt when devcontainer
182
+ # files change. Safe here because the per-devname queue is serial — a rebuild
183
+ # only happens between tasks, never mid-task.
184
+ extra_env = None
185
+ if devs_options:
186
+ extra_env = devs_options.get_env_vars(dev_name)
187
+
188
+ if not container_manager.ensure_container_running(
189
+ dev_name=dev_name,
190
+ workspace_dir=workspace_dir,
191
+ force_rebuild=False,
192
+ debug=self.config.dev_mode,
193
+ extra_env=extra_env,
194
+ ):
195
+ return False, "", f"Failed to start container for {dev_name}", 1
196
+
197
+ # 4. Build Claude prompt
181
198
  workspace_name = project.get_workspace_name(dev_name)
182
199
  workspace_path = f"/workspaces/{workspace_name}"
183
200
  repo_name = event.repository.full_name
@@ -277,14 +294,10 @@ Always remember to PUSH your work to origin!
277
294
  prompt_length=len(prompt),
278
295
  event_type="PR" if is_pr else "Issue")
279
296
 
280
- # 4. Execute Claude (like CLI pattern) with environment variables from DEVS.yml
297
+ # 5. Execute Claude (like CLI pattern) with environment variables from DEVS.yml
281
298
  logger.info("Executing Claude via ContainerManager (like CLI)",
282
299
  container=dev_name)
283
300
 
284
- extra_env = None
285
- if devs_options:
286
- extra_env = devs_options.get_env_vars(dev_name)
287
-
288
301
  # Start container logging if enabled
289
302
  if container_log:
290
303
  container_log.start(prompt=prompt, workspace_dir=str(workspace_dir))
@@ -369,6 +369,16 @@ class ContainerPool:
369
369
  # Determine which container to use
370
370
  best_container = None
371
371
 
372
+ # Load score = queued tasks + 1 if a task is currently running on the
373
+ # container. Without the running-task term, qsize() reads 0 for a
374
+ # busy-but-not-backlogged container (the worker pulls off the queue
375
+ # immediately) and the first item in target_pool wins every tie,
376
+ # pinning all traffic to one devname.
377
+ def _load(dev_name: str) -> int:
378
+ queued = self.container_queues[dev_name].qsize()
379
+ running = 1 if dev_name in self.running_containers else 0
380
+ return queued + running
381
+
372
382
  if single_queue_required:
373
383
  # Use the previously assigned container for this single-queue repo
374
384
  if repo_name in self.single_queue_assignments:
@@ -389,12 +399,7 @@ class ContainerPool:
389
399
 
390
400
  if best_container is None:
391
401
  # First time for this single-queue repo or need reassignment
392
- min_queue_size = float('inf')
393
- for dev_name in target_pool:
394
- queue_size = self.container_queues[dev_name].qsize()
395
- if queue_size < min_queue_size:
396
- min_queue_size = queue_size
397
- best_container = dev_name
402
+ best_container = min(target_pool, key=_load)
398
403
 
399
404
  if best_container:
400
405
  self.single_queue_assignments[repo_name] = best_container
@@ -403,13 +408,8 @@ class ContainerPool:
403
408
  container=best_container,
404
409
  task_type=task_type)
405
410
  else:
406
- # Normal load balancing - find container with shortest queue in target pool
407
- min_queue_size = float('inf')
408
- for dev_name in target_pool:
409
- queue_size = self.container_queues[dev_name].qsize()
410
- if queue_size < min_queue_size:
411
- min_queue_size = queue_size
412
- best_container = dev_name
411
+ # Normal load balancing - lowest queued+running count in target pool
412
+ best_container = min(target_pool, key=_load) if target_pool else None
413
413
 
414
414
  if best_container is None:
415
415
  logger.error("No containers available for task queuing",
@@ -124,6 +124,15 @@ def serve(host: str, port: int, reload: bool, env_file: Path, dev: bool, source:
124
124
 
125
125
  # Start the appropriate task source
126
126
  if config.task_source == "webhook":
127
+ # admin_password is required only when actually serving the FastAPI app
128
+ # (the admin endpoints use BasicAuth). SQS-source operation doesn't need it.
129
+ if not config.dev_mode and not config.admin_password:
130
+ click.echo(
131
+ "❌ ADMIN_PASSWORD is required when serving the webhook FastAPI app "
132
+ "in production mode (set ADMIN_PASSWORD or enable DEV_MODE=true)."
133
+ )
134
+ exit(1)
135
+
127
136
  # Override config with CLI options
128
137
  actual_host = host or config.webhook_host
129
138
  actual_port = port or config.webhook_port
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devs-webhook
3
- Version: 3.3.6
3
+ Version: 3.4.1
4
4
  Summary: GitHub webhook handler for automated devcontainer operations with Claude Code
5
5
  Author: Dan Lester
6
6
  License-Expression: MIT
@@ -107,7 +107,6 @@ export AUTHORIZED_TRIGGER_USERS="danlester,admin" # Users who can trigger proce
107
107
  # Optional settings (with defaults)
108
108
  export CONTAINER_POOL="eamonn,harry,darren"
109
109
  export CONTAINER_TIMEOUT_MINUTES="30"
110
- export MAX_CONCURRENT_TASKS="3"
111
110
  export WEBHOOK_HOST="0.0.0.0"
112
111
  export WEBHOOK_PORT="8000"
113
112
  ```
@@ -446,7 +445,6 @@ The webhook handler automatically detects and uses these settings when processin
446
445
  | `AUTHORIZED_TRIGGER_USERS` | (empty - allows all) | Comma-separated list of users who can trigger events |
447
446
  | `CONTAINER_POOL` | `eamonn,harry,darren` | Container names |
448
447
  | `CONTAINER_TIMEOUT_MINUTES` | `30` | Container timeout |
449
- | `MAX_CONCURRENT_TASKS` | `3` | Max parallel tasks |
450
448
  | `REPO_CACHE_DIR` | `~/.devs-webhook/repos` | Repository cache |
451
449
  | `WORKSPACE_DIR` | `~/.devs-webhook/workspaces` | Container workspaces |
452
450
  | `WEBHOOK_HOST` | `0.0.0.0` | Server host |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "devs-webhook"
7
- version = "3.3.6"
7
+ version = "3.4.1"
8
8
  description = "GitHub webhook handler for automated devcontainer operations with Claude Code"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
File without changes
File without changes