actionagent 1.9.5 → 1.9.6

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.
@@ -17,6 +17,8 @@ module ActionAgent
17
17
  LOG_TAIL_BYTES = 8 * 1024
18
18
  LOG_PAGE_BYTES = 64 * 1024
19
19
  LOG_MAX_PAGE_BYTES = 1024 * 1024
20
+ # The most of a backend's error the boot status carries (see #boot_report).
21
+ BACKEND_ERROR_BYTES = 1_000
20
22
  UPDATABLE = %i[name default_ref start_url].freeze
21
23
 
22
24
  before_action :require_owner!
@@ -214,18 +216,20 @@ module ActionAgent
214
216
  # The current sandbox's boot, step by step (see
215
217
  # SandboxOrchestrator#boot_status), with the scrubbed tail of the step
216
218
  # that failed or is running, and the modes its browser can start in
217
- # (none when the backend runs no browsers).
219
+ # (none when the backend runs no browsers). When the backend cannot
220
+ # report the boot, `boot` is null and `backend_error` names the backend
221
+ # and the reason (see #boot_report).
218
222
  def boot_status
219
223
  sandbox = @project.current_sandbox_session
220
224
  orchestrator = SandboxOrchestrator.new
221
- status = sandbox && orchestrator.supports?(:boot_status) ? orchestrator.boot_status(sandbox) : nil
222
- status = SecretScrubber.scrub(status.deep_symbolize_keys, @project.scrub_values) if status.is_a?(Hash)
225
+ status, log_tail, backend_error = boot_report(orchestrator, sandbox)
223
226
 
224
227
  render json: {
225
228
  project: @project.summary,
226
229
  boot: status,
227
- log_tail: status && log_tail(orchestrator, sandbox, status),
230
+ log_tail: log_tail,
228
231
  error: sandbox&.failed? ? SecretScrubber.scrub(sandbox.error_summary, @project.scrub_values) : nil,
232
+ backend_error: backend_error,
229
233
  logs: sandbox.present? && orchestrator.supports?(:boot_log),
230
234
  confirmation: @project.local_boot_confirmation(orchestrator),
231
235
  browser_modes: browser_modes(orchestrator)
@@ -535,6 +539,27 @@ module ActionAgent
535
539
  []
536
540
  end
537
541
 
542
+ # The backend's account of the sandbox's boot, scrubbed, with the tail
543
+ # of the step's log that goes with it: [status, log_tail, nil]. When the
544
+ # backend cannot give one (no host configured, the host unreachable, a
545
+ # container that has not written its state yet), [nil, nil, error],
546
+ # where error names the backend and the scrubbed reason. The page then
547
+ # shows the boot as unknown for that reason and keeps following it,
548
+ # instead of failing to load the project (#650). The sandbox's own
549
+ # failure, if it failed, is reported beside it as `error`.
550
+ def boot_report(orchestrator, sandbox)
551
+ return [ nil, nil, nil ] unless sandbox && orchestrator.supports?(:boot_status)
552
+
553
+ status = orchestrator.boot_status(sandbox)
554
+ status = SecretScrubber.scrub(status.deep_symbolize_keys, @project.scrub_values) if status.is_a?(Hash)
555
+ [ status, status && log_tail(orchestrator, sandbox, status), nil ]
556
+ rescue StandardError => e
557
+ Rails.logger.warn("[ActionAgent] the #{orchestrator.backend_name} sandbox backend could not report the boot of " \
558
+ "sandbox #{sandbox.session_id}: #{e.class}: #{e.message}")
559
+ [ nil, nil, { backend: orchestrator.backend_name,
560
+ message: SecretScrubber.scrub(e.message.to_s, @project.scrub_values).truncate(BACKEND_ERROR_BYTES) } ]
561
+ end
562
+
538
563
  # The last LOG_TAIL_BYTES of the step that failed, else of the one
539
564
  # running, else of the last that ran: { step:, text:, truncated: }.
540
565
  def log_tail(orchestrator, sandbox, status)
@@ -211,18 +211,19 @@ module ActionAgent
211
211
  # GET /api/sandboxes/:session_id/boot
212
212
  # How a checkout's boot went, step by step (see
213
213
  # SandboxOrchestrator#boot_status). `boot` is null when the backend
214
- # reports no steps, or holds nothing for the sandbox.
214
+ # reports no steps, or holds nothing for the sandbox; when the backend
215
+ # cannot report it at all, `backend_error` names the backend and the
216
+ # reason (see #boot_report).
215
217
  def boot
216
218
  orchestrator = SandboxOrchestrator.new
217
- status = @sandbox.app_runtime? && orchestrator.supports?(:boot_status) ? orchestrator.boot_status(@sandbox) : nil
218
- # A project's checkout booted with its secrets, which the backend may
219
- # not know to mask.
220
- status = SecretScrubber.scrub(status, @sandbox.project_scrub_values) if status
221
- render json: {
219
+ status, backend_error = boot_report(orchestrator)
220
+ payload = {
222
221
  boot: status,
223
222
  resumable: !!(status&.dig(:kept) && @sandbox.failed? && !@sandbox.past_expiry? && orchestrator.supports?(:resume_boot)),
224
223
  logs: @sandbox.app_runtime? && orchestrator.supports?(:boot_log)
225
224
  }
225
+ payload[:backend_error] = backend_error if backend_error
226
+ render json: payload
226
227
  end
227
228
 
228
229
  # GET /api/sandboxes/:session_id/boot_log?step=NAME&offset=N&limit=N
@@ -298,6 +299,30 @@ module ActionAgent
298
299
 
299
300
  private
300
301
 
302
+ # The most of a backend's error the boot status carries (see #boot_report).
303
+ BACKEND_ERROR_BYTES = 1_000
304
+
305
+ # The backend's account of the sandbox's boot, scrubbed of its
306
+ # project's secrets (a project's checkout booted with them, which the
307
+ # backend may not know to mask): [status, nil]. When the backend cannot
308
+ # give one (no host configured, the host unreachable, a container that
309
+ # has not written its state yet), [nil, error], where error names the
310
+ # backend and the scrubbed reason, so the page reports it instead of
311
+ # failing on a 500 (#650; Api::ProjectsController#boot_report is the
312
+ # same for a project).
313
+ def boot_report(orchestrator)
314
+ return [ nil, nil ] unless @sandbox.app_runtime? && orchestrator.supports?(:boot_status)
315
+
316
+ status = orchestrator.boot_status(@sandbox)
317
+ status = SecretScrubber.scrub(status, @sandbox.project_scrub_values) if status
318
+ [ status, nil ]
319
+ rescue StandardError => e
320
+ Rails.logger.warn("[ActionAgent] the #{orchestrator.backend_name} sandbox backend could not report the boot of " \
321
+ "sandbox #{@sandbox.session_id}: #{e.class}: #{e.message}")
322
+ [ nil, { backend: orchestrator.backend_name,
323
+ message: SecretScrubber.scrub(e.message.to_s, @sandbox.project_scrub_values).truncate(BACKEND_ERROR_BYTES) } ]
324
+ end
325
+
301
326
  def set_sandbox
302
327
  @sandbox = account_scoped(owned(SandboxSession)).find_by!(session_id: params[:id])
303
328
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionAgent
4
- VERSION = "1.9.5"
4
+ VERSION = "1.9.6"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: actionagent
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.5
4
+ version: 1.9.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Bowen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-10-28 00:00:00.000000000 Z
11
+ date: 2026-10-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activeagent