Skip to content

task run: the completion record's location is not settable, so a consumer must read two places #646

Closed

Description

@scttfrdmn

Split out of #620 so that issue can narrow to service --image.

The asymmetry

A task's results are fully caller-controlled; its terminal record is not.

outputs[].destination names its own destination per object, to any bucket — they need not share a bucket or prefix with each other or with anything spawn picked. cmd/task.go:521 is what makes that work: the launch role's inline policy is built from s3Buckets(spec.Outputs) and s3Buckets(spec.Inputs), so whatever buckets the spec names get access granted at launch. The cookbook is the standing proof — 79 recipes stage outputs into a bucket spawn never chose.

But completion.json and .exitcode always land at s3://spawn-results-<account>-<region>/tasks/<task_id>/:

cmd/task.go:471 resultsBucket := fmt.Sprintf("spawn-results-%s-%s", account, region) — hardcoded
pkg/taskproto/wrapper.go:83 resultsPrefix := fmt.Sprintf("s3://%s/tasks/%s", resultsBucket, taskID) — derived
pkg/taskproto/wrapper.go:111 only emits the value into the generated script

Verified there is no override: task run's complete flag set is --spec, --dry-run, --region, --ami, --wait, --poll-interval (cmd/task.go:1124-1129), and nothing matches SPAWN_RESULTS/results-bucket outside tests.

(Worth recording that an earlier comment on #620 claimed this was settable, reading wrapper.go:111's parameter as caller-controlled. That was wrong — the only caller computes it from account + region.)

Cost

A consumer that wants its own layout — e.g. foray's sessions/<id>/ — reads two locations: its own prefix for results, and the fixed spawn-results path for the terminal signal. Not a discovery gap: task run --help names that path literally. A capability gap.

Shape

A results_prefix field on the TaskSpec (or --results-prefix), defaulting to today's value so nothing changes for existing callers. It would need to flow to the same three places the bucket already does: the wrapper, the flush hook (#632), and taskStagingPolicy — the last one matters, because a prefix the role cannot write is a task that silently leaves no record.

Argument against, for the record

A fixed well-known location has one real virtue: completion.json is the single object guaranteed to appear even when the workload never got far enough to write a result — and since #632 it is also written when a lifecycle limit kills the task mid-command. A consumer polling for a terminal signal benefits from that path being unambiguous and independent of whatever the spec asked for. So this is a convenience trade, not a defect; worth weighing before implementing.

Activity

  1. added a commit that references this issue on Oct 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions