google-gemini/gemini-cli · error · Error

Invalid taskId: ${taskId}

Error message

Invalid taskId: ${taskId}

What it means

Thrown by GCSTaskStore.getObjectPath when isTaskIdValid(taskId) fails - the regex /^[a-zA-Z0-9_-]+$/ requires non-empty alphanumeric with only hyphens/underscores. This is a path-traversal guard: taskId is interpolated directly into the GCS object name `tasks/<id>/<type>.tar.gz`, so slashes, dots, or special chars could escape the tasks/ prefix. getObjectPath runs on every save/load.

Source

Thrown at packages/a2a-server/src/persistence/gcs.ts:89

        logger.info(`Bucket ${this.bucketName} exists.`);
      }
    } catch (error) {
      logger.info(
        `Error during bucket initialization for ${this.bucketName}: ${error}`,
      );
      throw new Error(
        `Failed to initialize GCS bucket ${this.bucketName}: ${error}`,
      );
    }
  }

  private async ensureBucketInitialized(): Promise<void> {
    await this.bucketInitialized;
  }

  private getObjectPath(taskId: string, type: ObjectType): string {
    if (!isTaskIdValid(taskId)) {
      throw new Error(`Invalid taskId: ${taskId}`);
    }
    return `tasks/${taskId}/${type}.tar.gz`;
  }

  async save(task: SDKTask): Promise<void> {
    await this.ensureBucketInitialized();
    const taskId = task.id;
    const persistedState = getPersistedState(
      // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
      task.metadata as PersistedTaskMetadata,
    );

    if (!persistedState) {
      throw new Error(`Task ${taskId} is missing persisted state in metadata.`);
    }
    const workDir = process.cwd();

    const metadataObjectPath = this.getObjectPath(taskId, 'metadata');

View on GitHub (pinned to 5024443c72)

Solutions

  1. Generate taskIds with a known-safe format: UUIDv4 (hyphens OK) or nanoid with the default alphabet.
  2. Sanitize incoming taskIds at the API boundary: strip/replace characters outside [A-Za-z0-9_-] before they reach the store.
  3. Reject the request at the handler with a 400 rather than letting the store throw.
  4. Add a unit test asserting your ID generator's output always matches /^[a-zA-Z0-9_-]+$/.

Example fix

// before
const id = `${userScope}:${crypto.randomUUID()}`;
store.save({ id, ... });
// throws: Invalid taskId (contains ':')

// after
const id = `${userScope}_${crypto.randomUUID().replaceAll('-', '_')}`;
// or validate at the boundary:
if (!/^[a-zA-Z0-9_-]+$/.test(id)) throw new TypeError('Invalid taskId');
store.save({ id, ... });
Defensive patterns

Strategy: validation

Validate before calling

const TASK_ID_RE = /^[a-zA-Z0-9_-]+$/;
function assertTaskIdValid(id: string) {
  if (!TASK_ID_RE.test(id)) throw new TypeError(`Invalid taskId '${id}'; expected ${TASK_ID_RE}`);
}
// call at the API boundary, before store.save/load

Type guard

function isValidTaskId(id: string): id is string {
  return /^[a-zA-Z0-9_-]+$/.test(id);
}

Try / catch

try {
  await store.save(task);
} catch (e) {
  if (e instanceof Error && e.message.startsWith('Invalid taskId')) {
    return { status: 400, error: e.message };
  }
  throw e;
}

Prevention

When it happens

Trigger: save(task) or load(taskId) is called with a taskId containing '/', '..', '.', spaces, or any non-[A-Za-z0-9_-] character. The regex rejects it before composing the object key, preventing traversal like `../../other-task/workspace`.

Common situations: Client supplies a taskId with a URL path segment or UUID with dots; SDK generates IDs that include ':' (common in some UUID variants); accidental concatenation like 'user:abc-123'; malicious input probing for traversal.

Related errors


AI-assisted analysis of google-gemini/gemini-cli@5024443c72 (2026-08-12). Data as JSON: /api/errors/4aecd8b91c8c84e1. Report an issue: GitHub.