Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Address work-queue storage removal review feedback
Reject stale runtime selectors before queue access, provide explicit removed-field diagnostics for workflows and imports, and regenerate the full schema reference with regression coverage.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
  • Loading branch information
pelikhan and Copilot committed Oct 8, 2026
commit 706ab56bd6ffc18b66ed92a8bf949289d28dad71
3 changes: 3 additions & 0 deletions .changeset/major-remove-work-queue-storage.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions actions/setup/js/work_queue_documentation.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ function readRepositoryFile(file) {
}

describe("work-queue deployment documentation", () => {
it("omits removed storage selectors from the generated frontmatter reference", () => {
const source = readRepositoryFile("docs/src/content/docs/reference/frontmatter-full.md");
const queue = source.slice(source.indexOf(" # Read the immutable version-3 activation snapshot."), source.indexOf(" # Cache memory MCP configuration"));
expect(queue).toContain("work-queue: true");
expect(queue).toContain("worker: true");
expect(queue).not.toMatch(/\bstorage:/);
});

it("installs the documented policy after replacing identity placeholders", () => {
const source = readRepositoryFile(deploymentPath);
const examples = [...source.matchAll(/```json(?: [^\n]*)?\n([\s\S]*?)\n```/g)];
Expand Down
56 changes: 34 additions & 22 deletions actions/setup/js/work_queue_store.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ const DEFAULT_MAX_RETRIES = 5;

/** @typedef {{githubClient: Parameters<typeof verifyRepository>[0], owner: string, repo: string, branch?: string, core?: import("./work_queue_summary_renderer.cjs").SummaryCore & {info(message: string): void}}} QueueReadOptions */

function rejectStorageSelector(options) {
if (Object.hasOwn(options, "storage") || Object.hasOwn(process.env, "GH_AW_WORK_QUEUE_STORAGE"))
throw queueError("unsupported_backend", "work-queue storage selectors have been removed. Git is the only supported backend; remove the storage option and GH_AW_WORK_QUEUE_STORAGE environment variable");
}

function validateBranch(branch) {
identity(branch, "queue branch");
if (LEGACY_BRANCHES.includes(branch)) throw queueError("unsupported_protocol", "legacy queue branches are unsupported; explicit new queue initialization is required");
Expand Down Expand Up @@ -103,7 +108,9 @@ async function readRef(githubClient, owner, repo, branch) {
}

/** @param {QueueReadOptions} options */
async function readWorkQueueLog({ githubClient, owner, repo, branch = WORK_QUEUE_BRANCH, core: coreApi = undefined }) {
async function readWorkQueueLog(options) {
rejectStorageSelector(options);
const { githubClient, owner, repo, branch = WORK_QUEUE_BRANCH, core: coreApi = undefined } = options;
log.debug("ledger.read.start");
validateBranch(branch);
const repository = await verifyRepository(githubClient, owner, repo);
Expand Down Expand Up @@ -170,7 +177,9 @@ function validateExtendingPrefix(previous, current, message) {
* @param {QueueReadOptions & {context: Parameters<typeof actorFromContext>[0]}} options
* @returns {(resource: unknown) => Promise<ReturnType<typeof validateClaimAuthority>>}
*/
function freshAuthorizer({ githubClient, owner, repo, context, branch = WORK_QUEUE_BRANCH, core: coreApi = undefined }) {
function freshAuthorizer(options) {
rejectStorageSelector(options);
const { githubClient, owner, repo, context, branch = WORK_QUEUE_BRANCH, core: coreApi = undefined } = options;
validateBranch(branch);
const actor = actorFromContext(context);
const trusted = validateTrustedContext(context, actor);
Expand Down Expand Up @@ -221,26 +230,28 @@ async function writeCandidate({ githubClient, owner, repo, current, transactions
* commitId?: typeof proposedCommitId, sleepFn?: (delay: number) => Promise<void>
* }} options
*/
async function publishWorkQueueRequest({
githubClient,
owner,
repo,
branch = WORK_QUEUE_BRANCH,
request,
context,
actor = actorFromContext(context),
generateOperations = generateRequestOperations,
refreshObservations = undefined,
remediationVerifier = undefined,
policyProposal = undefined,
initializationContext = undefined,
initializeOnly = false,
maxRetries = DEFAULT_MAX_RETRIES,
now = Date.now,
commitId = proposedCommitId,
sleepFn = delay => new Promise(resolve => setTimeout(resolve, delay)),
core: coreApi = undefined,
}) {
async function publishWorkQueueRequest(options) {
rejectStorageSelector(options);
const {
githubClient,
owner,
repo,
branch = WORK_QUEUE_BRANCH,
request,
context,
actor = actorFromContext(context),
generateOperations = generateRequestOperations,
refreshObservations = undefined,
remediationVerifier = undefined,
policyProposal = undefined,
initializationContext = undefined,
initializeOnly = false,
maxRetries = DEFAULT_MAX_RETRIES,
now = Date.now,
commitId = proposedCommitId,
sleepFn = delay => new Promise(resolve => setTimeout(resolve, delay)),
core: coreApi = undefined,
} = options;
log.debug("request.publish.start", { max_retries: maxRetries, initialize_only: initializeOnly });
validateBranch(branch);
integer(maxRetries, 0, 10, "publication retries");
Expand Down Expand Up @@ -362,6 +373,7 @@ function applyAndPublishWorkQueueTransactions(options) {
}

async function initializeWorkQueue(options) {
rejectStorageSelector(options);
log.debug("queue.initialize.start");
const actor = actorFromContext(options.context);
if (actor.role !== "administrator") throw queueError("actor_unauthorized", "trusted Policy initialization requires administrator credentials");
Expand Down
24 changes: 24 additions & 0 deletions actions/setup/js/work_queue_store_checks.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,30 @@ function nativeTarget(number = "7") {

function registerTests({ describe, it }) {
describe("checked current-only Git queue CAS store", () => {
it("rejects every removed storage option or environment selector before reading or publishing a queue", async () => {
const fake = fakeGitHub([genesis()]);
const request = newRequest("removed-storage", "control", administrator, { operations: [{ kind: "Control", control: "grants_paused", value: true, reason: "maintenance" }] });
const input = options(fake, request, administrator);
const check = async candidate => {
await assert.rejects(readWorkQueueLog(candidate), { code: "unsupported_backend" });
await assert.rejects(publishWorkQueueRequest(candidate), { code: "unsupported_backend" });
await assert.rejects(initializeWorkQueue(candidate), { code: "unsupported_backend" });
assert.throws(() => freshAuthorizer(candidate), { code: "unsupported_backend" });
assert.deepEqual(fake.state.calls, []);
assert.equal(fake.state.updates, 0);
};
for (const storage of ["git", "issues", "", null, undefined, false]) await check({ ...input, storage });
const previous = process.env.GH_AW_WORK_QUEUE_STORAGE;
try {
for (const storage of ["git", "issues", ""]) {
process.env.GH_AW_WORK_QUEUE_STORAGE = storage;
await check(input);
}
} finally {
if (previous === undefined) delete process.env.GH_AW_WORK_QUEUE_STORAGE;
else process.env.GH_AW_WORK_QUEUE_STORAGE = previous;
}
});
it("validates canonical trusted principals before no-op generation or any queue API probe", async () => {
const parameters = { pool: "default", max_claims: 1, max_dispatches: 1, max_bytes: 49152 };
const request = newRequest("principal-no-op", "dispatch_next", dispatcher, parameters);
Expand Down
68 changes: 46 additions & 22 deletions docs/src/content/docs/reference/frontmatter-full.md
Original file line number Diff line number Diff line change
Expand Up @@ -889,6 +889,25 @@ on:
forks: []
# Array items: string

# Acknowledge elevated permissions and secret access to suppress only the
# strict-mode pull_request_target trigger warning. Checkout validation remains
# enforced.
# (optional)
acknowledge-risk: true

# Exact literal repository/ref pairs trusted by the maintainer in addition to the
# supported base-repository checkouts. Expressions, wildcards, omitted refs, and
# PR refs are not allowed.
# (optional)
allowed-checkouts: []
# Array items:
# Literal owner/repository slug. Matches exactly, including case.
repository: "example-value"

# Literal trusted branch, tag, or commit SHA; not a pull request ref. Prefer an
# immutable commit SHA.
ref: "example-value"

# Pull request review event trigger that runs when a pull request review is
# submitted, edited, or dismissed
# (optional)
Expand Down Expand Up @@ -2706,8 +2725,16 @@ engine:
# (optional)
model: "example-value"

# Experimental per-run Copilot model and reasoning-effort selection from the task
# text; requires the AWF firewall and ignores fixed model and effort settings. See
# Ordered alternative models after model-specific failures, supported by engines
# with a built-in retry harness. Each model receives its own harness retry budget.
# Cannot be combined with model-routing or a custom driver or harness.
# (optional)
fallback-models: []
# Array of strings

# Experimental per-run GitHub Copilot model and reasoning-effort selection from
# the task text for Copilot, Claude, Codex, and pi engines; requires the AWF
# firewall and ignores fixed model and effort settings. See
# https://github.github.com/gh-aw/reference/model-routing/.
# (optional)
model-routing:
Expand Down Expand Up @@ -4436,10 +4463,6 @@ tools:

# Format 3: object
work-queue:
# Version-3 Git queue backend.
# (optional)
storage: "git"

# Fail closed and block safe outputs when no trusted inbound worker assignment is
# present.
# (optional)
Expand All @@ -4455,34 +4478,27 @@ tools:
# (optional)
memory:
# (optional)
name: "persist_work_queue_memory"
name: "My Workflow"

# Fixed relative JSON file path in the snapshot tree.
path: "memory.json"
path: "example-value"

# Fixed repository also authorized by the installed immutable Work scope.
target-repo: "owner/repo"
target-repo: "example-value"

base-revision: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
base-revision: "example-value"

branch-prefix: "memory/runs"
branch-prefix: "example-value"

# (optional)
max-bytes: 262144
max-bytes: 1

# Bounded inline typed JSON Schema subset: object
# properties/required/additionalProperties, array items, scalar enum, numeric
# ranges, and string/array length bounds. No references, regexes, or executable
# validators.
schema:
type: object

# `false` and `storage: "issues"` are invalid. Omit `work-queue` entirely for a
# workflow that does not use the queue. Frontmatter configures the workflow's
# compiler/runtime role; only authenticated installation of the queue Policy
# grants producer and worker-profile authority. Dispatch workers are additionally
# constrained by `safe-outputs.dispatch-workflow.workflows` and the installed
# profile's immutable workflow revision and principal.
{}

# Cache memory MCP configuration for persistent memory storage
# (optional)
Expand Down Expand Up @@ -21233,8 +21249,16 @@ safe-outputs:
# (optional)
model: "example-value"

# Experimental per-run Copilot model and reasoning-effort selection from the task
# text; requires the AWF firewall and ignores fixed model and effort settings. See
# Ordered alternative models after model-specific failures, supported by engines
# with a built-in retry harness. Each model receives its own harness retry budget.
# Cannot be combined with model-routing or a custom driver or harness.
# (optional)
fallback-models: []
# Array of strings

# Experimental per-run GitHub Copilot model and reasoning-effort selection from
# the task text for Copilot, Claude, Codex, and pi engines; requires the AWF
# firewall and ignores fixed model and effort settings. See
# https://github.github.com/gh-aw/reference/model-routing/.
# (optional)
model-routing:
Expand Down
3 changes: 3 additions & 0 deletions docs/src/content/docs/reference/work-queue.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ Git storage. Lightweight issue checklists and sub-issue queues use
outputs. This reference describes the native version-3 protocol. Git is the only
backend, and `tools.work-queue` has no `storage` field. The compiler rejects that
field for every value, including `git`; remove it from existing workflows.
The runtime also rejects a supplied `storage` option or
`GH_AW_WORK_QUEUE_STORAGE` environment variable for every value, including
`git`. Remove stale selectors and recompile instead of switching queues implicitly.

The native queue enforces fair scheduling, immutable task dependency graphs,
and worker effects authorized by individual Claims. Its only source of
Expand Down
5 changes: 5 additions & 0 deletions docs/src/content/docs/specs/work-queue-specification.md
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,11 @@ One scheduling **pool** is one durable decision domain with a fixed worker-capab

The protocol uses the **Git backend** exclusively. `tools.work-queue` MUST NOT expose a `storage` selector; the compiler MUST reject that field for every value, including `git`, without legacy compatibility or automatic migration. The Git backend MUST enforce the full scheduler contract; advisory display, unscheduled FIFO operation, or weakened consistency is not a permitted fallback.

The runtime MUST reject a supplied `storage` option or
`GH_AW_WORK_QUEUE_STORAGE` environment variable for every value before reading,
publishing, initializing, or authorizing queue effects. Stale selectors MUST NOT
silently switch the authoritative queue to Git.

Support exactly these scheduling modes:

| Mode | Selection |
Expand Down
13 changes: 13 additions & 0 deletions pkg/parser/schema_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,9 @@ func ValidateMainWorkflowFrontmatterWithSchemaAndLocation(frontmatter map[string
if err := validateMetadataDocs(filtered); err != nil {
return err
}
if err := validateRemovedWorkQueueStorage(filtered); err != nil {
return err
}

// Then run the standard schema validation with location
if err := validateWithSchemaAndLocation(filtered, mainWorkflowSchema, "main workflow file", filePath); err != nil {
Expand All @@ -201,6 +204,13 @@ func ValidateMainWorkflowFrontmatterWithSchemaAndLocation(frontmatter map[string
return validateEngineSpecificRules(filtered)
}

func validateRemovedWorkQueueStorage(frontmatter map[string]any) error {
if fieldExistsAtPath(frontmatter, []string{"tools", "work-queue", "storage"}) {
return errors.New("tools.work-queue.storage has been removed. Git is the only supported backend. Remove storage from tools.work-queue. Example:\ntools:\n work-queue: true")
}
return nil
}

func validateMetadataDocs(frontmatter map[string]any) error {
metadata, ok := frontmatter["metadata"].(map[string]any)
if !ok {
Expand Down Expand Up @@ -244,6 +254,9 @@ func ValidateIncludedFileFrontmatterWithSchemaAndLocation(frontmatter map[string
if err := validateMetadataDocs(filtered); err != nil {
return err
}
if err := validateRemovedWorkQueueStorage(filtered); err != nil {
return err
}

// To validate shared workflows against the main schema, we temporarily add an 'on' field
tempFrontmatter := make(map[string]any)
Expand Down
22 changes: 22 additions & 0 deletions pkg/parser/schema_work_queue_policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,28 @@ func TestWorkQueuePolicySchema(t *testing.T) {
}, "worker.md"))
}

func TestWorkQueueStorageRemovalMessage(t *testing.T) {
for _, storage := range []any{"git", "issues", nil, false} {
err := ValidateMainWorkflowFrontmatterWithSchemaAndLocation(map[string]any{
"on": "workflow_dispatch",
"tools": map[string]any{"work-queue": map[string]any{
"storage": storage, "worker": true,
}},
}, "worker.md")
require.ErrorContains(t, err, "tools.work-queue.storage has been removed")
require.ErrorContains(t, err, "Git is the only supported backend")
require.ErrorContains(t, err, "Remove storage from tools.work-queue")
require.ErrorContains(t, err, "work-queue: true")
require.NotContains(t, err.Error(), "experiments")
require.ErrorContains(t, ValidateIncludedFileFrontmatterWithSchemaAndLocation(map[string]any{
"tools": map[string]any{"work-queue": map[string]any{"storage": storage}},
}, "shared.md"), "tools.work-queue.storage has been removed")
}
require.NoError(t, ValidateMainWorkflowFrontmatterWithSchemaAndLocation(map[string]any{
"on": "workflow_dispatch", "experiments": map[string]any{"storage": "repo"},
}, "experiments.md"))
}

func TestWorkQueueMemorySchema(t *testing.T) {
memory := map[string]any{"path": "memory.json", "target-repo": "owner/repo", "base-revision": strings.Repeat("a", 40), "branch-prefix": "memory/runs", "schema": map[string]any{"type": "object"}}
validate := func(config map[string]any) error {
Expand Down
1 change: 1 addition & 0 deletions pkg/parser/schemas/main_workflow_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -5225,6 +5225,7 @@
"description": "Read the immutable version-3 activation snapshot. Nonworkers without approved dispatch targets are read-only observers; workers and approved dispatchers may stage scoped queue intents. The installed ledger Policy is authoritative. Only Git storage is supported.",
"oneOf": [
{
"type": "boolean",
"const": true,
"description": "Enable read-only queue observation, or queue-control intents when approved worker dispatch targets are configured."
},
Expand Down
4 changes: 3 additions & 1 deletion pkg/workflow/work_queue_compilation_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,9 @@ Read and finish assigned work.
compiler := NewCompiler(WithVersion("integration"))
compiler.SetApprove(true)
err := compiler.CompileWorkflow(workflowPath)
require.ErrorContains(t, err, "storage")
require.ErrorContains(t, err, "tools.work-queue.storage has been removed")
require.ErrorContains(t, err, "Remove storage from tools.work-queue")
require.NotContains(t, err.Error(), "experiments")
_, err = os.Stat(filepath.Join(dir, "worker.lock.yml"))
require.True(t, os.IsNotExist(err), "removed storage field must not produce a lock file")
})
Expand Down
Loading